🧩 feat(shared): definir fronteiras de dados e configuracao da fase 4

Formaliza os contratos compartilhados para leitura operacional, estrategia de relatorios e configuracao funcional governada entre admin e product.

Tambem separa explicitamente o runtime do bot de atendimento do runtime de geracao de tools, detalha quais configuracoes do bot entram sob governanca administrativa e documenta as regras de publicacao, rollback e leitura sem acoplar o hot path do atendimento.
feat/self-evolving-tools-foundation
parent bd662f35fa
commit 5ca21b598f

@ -0,0 +1,108 @@
# Configuracoes Do Bot Governadas Pelo Admin
## Objetivo
Definir exatamente quais configuracoes do bot de atendimento entram sob governanca do `orquestrador-admin`.
Esta etapa detalha, em nivel de campo, a parte do runtime do bot que pode ser consultada por `colaborador` e alterada por `diretor`.
## Decisao
O `admin` governa apenas configuracoes funcionais do bot de atendimento.
Isso inclui:
- escolha do modelo homologado usado no atendimento
- politicas de resposta do bot
- politicas de uso de tools
- politicas de fallback e handoff humano
- politicas operacionais por canal
Essa fronteira fica formalizada em `shared/contracts/bot_governed_configuration.py`.
## Configuracoes governadas
### 1. Selecao de modelo do bot
Campos governados:
- `provider`
- `model_name`
Esses campos definem qual modelo homologado responde ao cliente final.
### 2. Geracao de resposta
Campos governados:
- `temperature`
- `max_output_tokens`
- `prompt_profile_ref`
Esses campos controlam o perfil funcional da resposta, sem expor o painel a segredos ou internals de infraestrutura.
### 3. Uso de tools
Campos governados:
- `tool_policy_ref`
- `max_tool_calls_per_turn`
- `confirmation_policy`
Esses campos definem como o bot pode usar tools e quando precisa de confirmacao antes de acao critica.
### 4. Fallback e handoff
Campos governados:
- `fallback_mode`
- `handoff_enabled`
- `handoff_intents`
Esses campos governam quando o fluxo segue fallback controlado e quando encaminha para atendimento humano.
### 5. Operacao por canal
Campos governados:
- `enabled`
- `maintenance_mode`
- `default_route`
- `operation_window_ref`
Esses campos permitem controlar disponibilidade e comportamento funcional por canal homologado.
## O que nao entra como configuracao do bot
As seguintes superficies ficam fora desta governanca:
- configuracao de modelo para geracao de tools
- credenciais de provedor e segredos
- conteudo bruto de prompt sensivel
- variaveis de ambiente e infraestrutura
- implementacao interna das tools
- alteracao direta em tabelas operacionais do `product`
## Regras obrigatorias
### 1. Leitura por `colaborador`, alteracao por `diretor`
- `colaborador` consulta via `view_system`
- `diretor` consulta e altera via `manage_settings`
### 2. Sem escrita direta no runtime do produto
O painel registra estado desejado e governado.
O `product` consome apenas configuracao publicada, versionada e auditavel.
### 3. Separacao do runtime de geracao
O runtime usado para gerar tools continua em trilha propria.
Ele nao deve ser tratado como configuracao do bot de atendimento.
## Consequencias positivas
- deixa a tela de configuracao do bot mais clara e segura
- evita que a UI misture atendimento com geracao de tools
- preserva a governanca de publicacao entre `admin` e `product`
- prepara a proxima etapa de rotas administrativas para configuracao funcional do sistema

@ -0,0 +1,197 @@
# Escopo De Configuracao Funcional Governada No Admin
## Objetivo
Definir quais configuracoes funcionais o `orquestrador-admin` pode consultar e alterar sem transformar o painel em uma superficie de mudanca irrestrita do runtime do `orquestrador-product`.
Esta etapa fixa a **fronteira funcional de configuracao**.
As telas e rotas especificas da fase 4 vao consumir esse contrato depois.
## Decisao
O `admin` pode consultar um conjunto governado de configuracoes funcionais do sistema.
Dessas configuracoes, apenas o papel `diretor` pode alterar o estado desejado.
O papel `colaborador` fica com leitura para acompanhamento operacional do sistema.
A fronteira compartilhada inicial fica em `shared/contracts/system_functional_configuration.py`.
O detalhamento especifico do que o painel governa no bot de atendimento fica em `docs/architecture/admin-bot-governed-configuration-scope.md`.`r`nA separacao entre o runtime de atendimento e o runtime de geracao de tools fica em `docs/architecture/admin-model-runtime-separation.md`.
## O que entra na fronteira administrativa
As primeiras configuracoes funcionais aprovadas para o painel sao:
1. `allowed_model_catalog`
2. `atendimento_runtime_profile`
3. `tool_generation_runtime_profile`
4. `bot_behavior_policy`
5. `channel_operation_policy`
6. `published_runtime_state`
### 1. `allowed_model_catalog`
Superficie somente leitura usada para o painel saber quais modelos estao homologados pela plataforma.
Serve para:
- montar listas de selecao na tela administrativa
- impedir configuracao de modelo fora do catalogo permitido
- diferenciar modelos liberados para atendimento e para geracao de tools
Nao serve para:
- cadastrar credenciais de provedor
- alterar limites de infraestrutura
- homologar modelo novo diretamente pela UI
### 2. `atendimento_runtime_profile`
Configuracao funcional governada do modelo do bot que atende o cliente final.
Inclui:
- provedor selecionado
- modelo selecionado
- temperatura
- limite de saida
- referencia de prompt publicada
- referencia de politica de tools
Regra:
- `colaborador` consulta
- `diretor` altera
### 3. `tool_generation_runtime_profile`
Configuracao funcional governada do modelo usado para gerar e validar novas tools.
Inclui:
- provedor selecionado
- modelo selecionado
- perfil de raciocinio
- limite de saida
- referencia de politica de validacao
Regra obrigatoria:
- esse perfil e separado do perfil de atendimento
- trocar o modelo de geracao nao troca automaticamente o modelo do bot
### 4. `bot_behavior_policy`
Politicas funcionais do fluxo do bot.
Inclui:
- modo de fallback
- handoff para humano
- intencoes que forcam escalonamento
- limite de chamadas de tool por turno
- politica de confirmacao para acao critica
Essa configuracao existe para o painel governar o comportamento funcional do atendimento, e nao o codigo interno do orquestrador.
### 5. `channel_operation_policy`
Politicas funcionais por canal.
Inclui:
- canal habilitado ou desabilitado
- modo de manutencao
- rota funcional padrao
- referencia da janela operacional
Essa superficie permite governar disponibilidade funcional sem dar acesso a infraestrutura bruta.
### 6. `published_runtime_state`
Superficie somente leitura do estado efetivo publicado no `product`.
Inclui:
- escopo configurado
- versao ativa
- quem publicou
- quando publicou
- quando o produto aplicou a mudanca
Serve para:
- auditoria
- transparencia na dashboard
- comparacao entre estado desejado no admin e estado efetivo no produto
## O que fica fora da fronteira administrativa
As seguintes superficies nao entram como configuracao funcional alteravel no painel:
- segredos e credenciais de provedor
- API keys
- strings de conexao com banco
- variaveis de ambiente de deploy
- configuracao de autoscaling e infraestrutura
- schema de banco operacional
- payloads tecnicos internos de execucao
- alteracao direta em tabelas operacionais do `product`
## Regras obrigatorias
### 1. Leitura ampla, escrita governada
A leitura dessas configuracoes nasce sob `view_system`.
Consequencia pratica:
- `colaborador` pode consultar a configuracao funcional vigente
- `diretor` tambem consulta
- apenas `diretor` altera configuracoes governadas com `manage_settings`
### 2. Sem escrita direta no produto
O painel administrativo nao escreve diretamente no runtime do `product` durante uma request de UI.
A fronteira correta eh:
- o `admin` registra estado funcional desejado
- o estado e versionado, auditado e aprovado
- o `product` consome apenas configuracao publicada
### 3. Separacao entre atendimento e geracao de tools
Os dois runtimes precisam continuar independentes.
Portanto:
- o modelo do atendimento vive em `atendimento_runtime_profile`
- o modelo de geracao vive em `tool_generation_runtime_profile`
- cada perfil pode ter rollout, auditoria e fallback proprios
### 4. Estado efetivo precisa ser observavel
Toda configuracao governada precisa gerar uma superficie de consulta sobre o estado efetivo publicado no `product`.
Consequencia pratica:
- a dashboard administrativa consegue mostrar o que esta ativo de verdade
- o sistema evita divergencia silenciosa entre desejo do admin e runtime do produto
## Consequencias positivas
- permite escolher modelo do bot pelo painel sem expor segredos de infraestrutura
- prepara a tela de configuracoes do sistema para `diretor`
- mantem `colaborador` com visibilidade do fluxo do bot e do estado vigente
- reforca a separacao entre governanca administrativa e hot path do atendimento
- prepara versionamento e auditoria das configuracoes antes da integracao completa entre `admin` e `product`
## Proximos passos naturais
- criar rotas administrativas para configuracao funcional do sistema
- criar tela administrativa de configuracoes do sistema
- criar superficie visual para estado publicado e versoes ativas
- definir publicacao e consumo dessas configuracoes entre `admin` e `product`

@ -0,0 +1,90 @@
# Separacao Entre Modelo Do Atendimento E Modelo De Geracao De Tools
## Objetivo
Definir a fronteira entre o runtime de modelo usado no atendimento ao cliente e o runtime de modelo usado para gerar e validar novas tools.
Esta etapa consolida uma regra importante da arquitetura: os dois perfis de modelo nao podem compartilhar configuracao nem ciclo de publicacao.
## Decisao
O sistema passa a tratar esses runtimes como perfis independentes.
Perfis:
1. `atendimento_runtime_profile`
2. `tool_generation_runtime_profile`
A separacao formal fica em `shared/contracts/model_runtime_separation.py`.
## Regras obrigatorias
### 1. Configuracoes distintas
Cada runtime possui sua propria `config_key`.
Portanto:
- o atendimento usa `atendimento_runtime_profile`
- a geracao de tools usa `tool_generation_runtime_profile`
- uma mudanca de configuracao nunca reutiliza a mesma chave para os dois contextos
### 2. Catalogos com alvo separado
Os modelos homologados precisam carregar o alvo funcional correto.
Portanto:
- modelos homologados para atendimento entram sob `runtime_target = atendimento`
- modelos homologados para geracao entram sob `runtime_target = tool_generation`
- um modelo pode existir nos dois catalogos, mas a selecao continua independente
### 3. Publicacao independente
Os dois runtimes possuem publicacao independente.
Consequencia pratica:
- publicar uma mudanca no atendimento nao publica a geracao de tools
- publicar uma mudanca na geracao de tools nao muda o bot que responde ao cliente
- cada perfil pode ter sua propria auditoria e versao ativa
### 4. Rollback independente
Cada runtime precisa poder voltar ao estado anterior sem afetar o outro.
Consequencia pratica:
- rollback do atendimento nao mexe no runtime de geracao
- rollback da geracao nao mexe no atendimento em producao
### 5. Sem propagacao implicita
Nao e permitido que uma alteracao em um runtime seja espelhada automaticamente no outro.
Isso impede:
- trocar o modelo do bot e, por efeito colateral, trocar o modelo de geracao
- usar defaults compartilhados para empurrar mudancas silenciosas nos dois fluxos
- misturar SLO, custo e risco do atendimento com o pipeline de tools
## Responsabilidade por runtime
### Atendimento
- alvo funcional: responder ao cliente final
- servico consumidor: `product`
- impacto direto: experiencia do atendimento e fluxo conversacional
### Geracao de tools
- alvo funcional: gerar e validar novas tools
- servico consumidor: `admin`
- impacto direto: pipeline de governanca, geracao e validacao
## Consequencias positivas
- protege o atendimento de experimentos de geracao de codigo
- permite escolher modelos diferentes para custo, latencia e qualidade em cada fluxo
- simplifica auditoria e rollback de configuracao
- prepara as futuras telas e rotas de configuracao do sistema sem ambiguidade

@ -0,0 +1,299 @@
# Escopo De Dados Operacionais Do Product Visiveis No Admin
## Objetivo
Definir, de forma explicita, quais dados operacionais do `orquestrador-product` podem ser consultados pelo `orquestrador-admin` na fase inicial de relatorios e configuracao.
Esta definicao cobre o **que** o admin pode ler.
A estrategia de leitura desses dados sem acoplar o hot path do atendimento fica detalhada em `docs/architecture/admin-report-reading-strategy.md`.
A materializacao concreta desses relatorios fica detalhada em `docs/architecture/admin-report-materialization-strategy.md`.
## Principios obrigatorios
1. O `product` continua sendo a fonte operacional primaria.
2. O `admin` nasce com acesso de leitura orientado a relatorios, nunca como escritor direto dessas tabelas.
3. O hot path do atendimento nao deve depender de consulta online ao `admin`.
4. Dados de identidade do cliente final, texto livre e segredos operacionais nao entram automaticamente na fronteira administrativa.
5. Sempre que um indicador puder ser atendido por agregado, o agregado deve ser preferido a leitura detalhada.
## Datasets permitidos nesta fase
O contrato compartilhado correspondente fica em `shared/contracts/product_operational_data.py`.
### 1. Estoque comercial
Fonte atual:
- `vehicles`
Uso administrativo esperado:
- disponibilidade comercial
- distribuicao por categoria
- faixa de preco
- entrada de novos itens no estoque
Campos permitidos:
- `id`
- `modelo`
- `categoria`
- `preco`
- `created_at`
### 2. Pedidos de venda
Fonte atual:
- `orders`
Uso administrativo esperado:
- volume de pedidos
- pedidos ativos e cancelados
- ticket medio
- cancelamentos por periodo
Campos permitidos:
- `numero_pedido`
- `vehicle_id`
- `modelo_veiculo`
- `valor_veiculo`
- `status`
- `motivo_cancelamento`
- `data_cancelamento`
- `created_at`
- `updated_at`
Campos bloqueados:
- `user_id`
- `cpf`
### 3. Agenda de revisoes
Fonte atual:
- `review_schedules`
Uso administrativo esperado:
- ocupacao de slots
- revisoes agendadas por periodo
- taxa de cancelamento
- fila operacional da oficina
Campos permitidos:
- `protocolo`
- `placa`
- `data_hora`
- `status`
- `created_at`
Campos bloqueados:
- `user_id`
### 4. Frota de locacao
Fonte atual:
- `rental_vehicles`
Uso administrativo esperado:
- disponibilidade da frota
- status operacional por categoria
- tarifa diaria vigente
Campos permitidos:
- `id`
- `placa`
- `modelo`
- `categoria`
- `ano`
- `valor_diaria`
- `status`
- `created_at`
### 5. Contratos de locacao
Fonte atual:
- `rental_contracts`
Uso administrativo esperado:
- contratos ativos e encerrados
- devolucoes em atraso
- receita prevista versus receita final
- ocupacao da frota no tempo
Campos permitidos:
- `contrato_numero`
- `rental_vehicle_id`
- `placa`
- `modelo_veiculo`
- `categoria`
- `data_inicio`
- `data_fim_prevista`
- `data_devolucao`
- `valor_diaria`
- `valor_previsto`
- `valor_final`
- `status`
- `created_at`
- `updated_at`
Campos bloqueados:
- `user_id`
- `cpf`
- `observacoes`
### 6. Pagamentos de locacao
Fonte atual:
- `rental_payments`
Uso administrativo esperado:
- arrecadacao por periodo
- pagamentos conciliados por contrato
- inadimplencia operacional
Campos permitidos:
- `protocolo`
- `contrato_numero`
- `placa`
- `valor`
- `data_pagamento`
- `created_at`
Campos bloqueados:
- `user_id`
- `rental_contract_id`
- `favorecido`
- `identificador_comprovante`
- `observacoes`
### 7. Telemetria conversacional
Fonte atual:
- `conversation_turns`
Uso administrativo esperado:
- volume de atendimento
- latencia por turno
- distribuicao por dominio
- uso de tools
- falhas operacionais por status
Campos permitidos:
- `request_id`
- `conversation_id`
- `channel`
- `turn_status`
- `intent`
- `domain`
- `action`
- `tool_name`
- `elapsed_ms`
- `started_at`
- `completed_at`
Campos bloqueados:
- `user_id`
- `external_id`
- `username`
- `user_message`
- `assistant_response`
- `tool_arguments`
- `error_detail`
### 8. Entregas de integracao
Fonte atual:
- `integration_deliveries`
Uso administrativo esperado:
- taxa de sucesso por provedor
- volume de eventos entregues
- entregas pendentes ou com falha
- tentativas de reenvio
Campos permitidos:
- `route_id`
- `event_type`
- `provider`
- `status`
- `attempts`
- `dispatched_at`
- `created_at`
- `updated_at`
Campos bloqueados:
- `payload_json`
- `recipient_email`
- `recipient_name`
- `rendered_subject`
- `rendered_body`
- `provider_message_id`
- `last_error`
## Fontes fora do escopo administrativo nesta fase
O admin **nao** deve consultar diretamente, nesta fase:
- `customers`
- `users`
- stores de estado conversacional de hot path
- payloads brutos de tools e mensagens do usuario
- comprovantes e identificadores sensiveis de pagamento
- configuracoes internas de provedor e credenciais
## Regra de autorizacao
A leitura desses dados nasce amarrada a `view_reports`.
Consequencia pratica:
- `colaborador` pode consultar os dados operacionais liberados para relatorio
- `diretor` herda essa leitura e acumula as etapas de aprovacao e configuracao
- permissao adicional sera exigida apenas quando a consulta implicar governanca, aprovacao ou configuracao
## Decisao tomada nesta etapa
O `admin` pode consultar apenas datasets operacionais explicitamente declarados em contrato compartilhado e sempre em modo somente leitura.
A fronteira inicial favorece relatorios de:
- vendas
- arrecadacao
- operacao
- telemetria de atendimento
- entregas de integracao
## Decisao de materializacao relacionada
Para esses datasets, a fase inicial escolhe:
- `etl_incremental` como estrategia de sincronizacao
- `snapshot_table` no lado administrativo como persistencia de leitura
- `dedicated_view` sobre os snapshots como superficie de consulta para APIs e UI
- nenhuma replica operacional do banco do produto no dashboard administrativo

@ -0,0 +1,128 @@
# Estrategia De Materializacao Dos Relatorios Administrativos
## Objetivo
Escolher como os relatorios administrativos vao materializar o read model definido para a fase 4.
A decisao precisava fechar quatro alternativas candidatas:
- replica
- ETL
- snapshots
- views dedicadas
## Decisao
A fase inicial de relatorios do `orquestrador-admin` vai usar a seguinte composicao:
1. `etl_incremental` como mecanismo de sincronizacao
2. `snapshot_table` no lado administrativo como persistencia de leitura
3. `dedicated_view` sobre os snapshots como superficie de consulta para APIs e UI
4. nenhuma replica operacional do banco do `product` para abrir dashboards administrativos
Em resumo:
- **nao** usar replica como mecanismo primario da fase inicial
- **sim** usar ETL incremental
- **sim** persistir snapshots sanitizados
- **sim** expor views dedicadas sobre esses snapshots
## Por que nao comecar por replica
Replica isolaria menos do que parece.
Ela ainda manteria o admin muito proximo do schema operacional live, incentivando query ad hoc, joins pesados e acoplamento ao desenho interno do `product`.
Tambem traria custo operacional cedo demais:
- infraestrutura adicional
- observabilidade de replicacao
- risco de leitura errada por atraso ou schema drift
- falsa sensacao de que qualquer tabela do produto pode virar dashboard
Para a fase inicial, replica aumenta a superficie tecnica sem resolver a necessidade principal, que eh governar exatamente **o que** sai do produto e **como** isso chega ao admin.
## Por que ETL incremental
ETL incremental encaixa melhor no que ja decidimos para o sistema:
- preserva o hot path do atendimento
- permite sanitizacao e minimizacao antes do dado chegar ao admin
- suporta watermark, cursor e reprocessamento controlado
- facilita auditoria do ciclo de consolidacao
- prepara evolucao futura para jobs, workers ou pipelines por evento
O ETL aqui nao precisa nascer grande.
Ele pode comecar como job incremental simples e evoluir sem quebrar o contrato do painel.
## Por que snapshots
Snapshots sao a melhor base inicial de persistencia para relatorios administrativos porque:
- congelam um recorte coerente do dataset consolidado
- permitem metadados como `generated_at`, `source_watermark` e `dataset_version`
- reduzem risco de consultas inconsistentes durante sincronizacao
- simplificam retry, backfill e comparacao entre execucoes
Na pratica, os snapshots pertencem ao contexto administrativo, nao ao banco operacional do produto.
## Por que views dedicadas
Views dedicadas ficam por cima dos snapshots para desacoplar a UI e as APIs do formato bruto de consolidacao.
Elas permitem:
- esconder colunas tecnicas de ETL
- estabilizar o contrato consumido pelos relatorios
- organizar uma view por caso de uso de negocio
- evoluir agregacoes e joins internos sem quebrar a tela
Regra importante:
- essas views sao dedicadas ao contexto administrativo
- elas nao apontam para tabelas live do produto
- elas leem apenas snapshots ja sanitizados
## Fluxo alvo
```text
product operational tables
|
v
etl_incremental boundary
|
v
admin snapshot tables
|
v
admin dedicated views
|
v
admin report routes and dashboard
```
## Regras obrigatorias
1. O painel nunca consulta replica ou tabela live do produto durante request web.
2. O ETL incremental so exporta datasets e campos aprovados em contrato compartilhado.
3. Cada snapshot precisa carregar watermark e timestamp de geracao.
4. Cada view dedicada existe para um caso de uso de relatorio, nunca como espelho generico do schema operacional.
5. Escrita administrativa em tabela operacional do produto continua proibida.
## Consequencias praticas para a fase 4
Com essa decisao, os proximos itens da fase ficam orientados assim:
- rotas administrativas de relatorio devem ler views dedicadas do admin
- relatorios de vendas, arrecadacao e operacao devem nascer sobre snapshots sanitizados
- a UI deve exibir frescor e estado da ultima consolidacao
- qualquer refresh manual conversa com a camada de sincronizacao, nao com o banco operacional live
## Evolucao futura permitida
Se no futuro houver escala suficiente para replica, ela pode entrar como **fonte de extração** do ETL, e nao como backend direto do dashboard.
Ou seja:
- replica pode aparecer depois como detalhe de implementacao
- o contrato do painel continua o mesmo
- a fronteira principal segue sendo ETL -> snapshots -> views dedicadas

@ -0,0 +1,154 @@
# Estrategia De Leitura De Relatorios Sem Acoplar O Hot Path
## Objetivo
Definir como o `orquestrador-admin` deve ler dados operacionais para relatorios sem transformar o `orquestrador-product` em backend sincrono de dashboard.
Esta etapa fixa a **topologia de leitura**.
A materializacao concreta dessa topologia foi definida em `docs/architecture/admin-report-materialization-strategy.md`.
## Decisao
Os relatorios administrativos devem ser servidos a partir de um **read model administrativo assincrono**.
Em outras palavras:
1. O `product` continua escrevendo o estado operacional primario.
2. Uma camada de sincronizacao fora do hot path materializa dados de leitura para relatorio.
3. O `admin` consulta apenas esse read model, nunca as tabelas operacionais live do `product` em uma request web do painel.
## Topologia alvo
```text
product operational writes
|
v
sync/export boundary outside hot path
|
v
admin reporting read model
|
v
admin report APIs and dashboard
```
## Regras obrigatorias
### 1. Sem query direta do painel no banco operacional do produto
Nao e permitido que uma rota web do painel administrativo execute consultas pesadas ou agregacoes diretamente nas tabelas operacionais do `product`.
Isso inclui:
- scans amplos em `orders`, `rental_contracts`, `rental_payments`, `conversation_turns`
- joins ad hoc para dashboard em tempo de request
- leituras que disputem lock, cache ou I/O com o atendimento
### 2. Leitura eventual, nao transacional
O painel administrativo deve operar com **consistencia eventual**.
Consequencia pratica:
- relatorios mostram o dado consolidado mais recente disponivel
- a UI deve exibir metadados de frescor, como `updated_at`, `generated_at` ou `source_watermark`
- o sistema nao promete refletir cada evento operacional no mesmo instante em que ele acontece
### 3. Materializacao fora do hot path
Toda consolidacao, enriquecimento, agregacao ou recorte temporal deve acontecer em processo assincrono.
Exemplos validos de processo assincrono:
- job agendado
- worker orientado a eventos
- pipeline incremental por cursor
- rotina batch com watermark
### 4. Read model proprio do admin
O `admin` deve ter sua propria superficie de leitura para relatorios.
Essa superficie pode morar:
- no banco administrativo
- em um schema analitico separado
- em tabelas materializadas especificas para relatorio
O importante nesta etapa nao e o lugar fisico, e sim a regra:
- a query do painel le um read model pronto
- a transformacao do dado acontece antes da request do usuario interno
### 5. Escrita administrativa continua proibida
A estrategia de leitura nao muda a fronteira de escrita.
Portanto:
- o `admin` nao escreve diretamente nas tabelas operacionais do `product`
- qualquer acao de governanca que altere operacao deve seguir fluxo proprio, versionado e auditavel
## Responsabilidades por servico
### `orquestrador-product`
Responsavel por:
- persistir o estado operacional primario
- manter ids tecnicos, timestamps e chaves publicas necessarias para reconciliacao
- expor uma fronteira segura para exportacao ou sincronizacao
- continuar respondendo ao atendimento sem depender do `admin`
### `orquestrador-admin`
Responsavel por:
- armazenar ou consultar o read model de relatorio
- servir rotas administrativas de relatorio
- informar frescor e origem do dado para a UI
- aplicar filtros e agregacoes sobre a superficie de leitura consolidada
## O que a UI deve assumir
As telas administrativas de relatorio devem nascer com a expectativa de dado consolidado e nao de espelho instantaneo do operacional.
Consequencia pratica:
- cada relatorio deve carregar carimbo de atualizacao
- um refresh manual dispara no maximo uma rotina de sincronizacao, nunca uma query pesada live no produto
- empty states e avisos de defasagem fazem parte do contrato visual
## Padrao de frescor inicial
Enquanto a implementacao completa nao chega, os datasets operacionais ficam classificados em metas de frescor no contrato compartilhado:
- `near_real_time` para vendas, revisoes e locacao
- `intra_hour` para estoque, telemetria e entregas de integracao
Essas metas servem para orientar UX, monitoracao e futuras implementacoes da sincronizacao.
Elas nao significam leitura live do banco operacional.
## O que fica explicitamente proibido
- dashboard administrativo consultando `product` por HTTP sincrono a cada abertura de pagina
- admin executando agregacao pesada em banco primario de atendimento durante request web
- relatorios dependendo de lock em tabela operacional para responder ao usuario interno
- uso de payload bruto e PII fora do contrato compartilhado de dados operacionais
## Consequencias positivas
- protege latencia do atendimento
- reduz risco de regressao operacional por carga analitica
- permite evoluir relatorios com independencia do runtime conversacional
- facilita observabilidade de frescor e falha da sincronizacao
- prepara o terreno para ETL incremental, snapshots sanitizados e views dedicadas sem quebrar o painel
## Decisao complementar ja tomada
A topologia acima agora foi materializada assim:
- `etl_incremental` como fronteira de sincronizacao
- `snapshot_table` no admin para persistencia de leitura
- `dedicated_view` sobre snapshots para servir APIs e dashboard
- sem replica operacional do banco do produto nesta fase

@ -9,6 +9,8 @@ Criar uma base comum para:
- autenticacao e autorizacao administrativa
- publicacao de tools do `admin` para o `product`
- leitura operacional segura do `admin` sobre o `product`
- configuracao funcional governada entre `admin` e `product`
- evolucao independente dos dois servicos sem acoplamento indevido
## Hierarquia inicial de acesso
@ -17,37 +19,28 @@ Os papeis administrativos ficam centralizados em `shared/contracts/access_contro
Hierarquia:
1. `viewer`
2. `staff`
3. `admin`
1. `colaborador`
2. `diretor`
### `viewer`
### `colaborador`
Responsavel por leitura operacional.
Responsavel por operacao interna de acompanhamento e cadastro inicial de tools.
Permissoes iniciais:
- `view_system`
- `view_reports`
- `view_audit_logs`
### `staff`
Responsavel por operacao interna e governanca de drafts.
Permissoes iniciais:
- todas as de `viewer`
- `manage_tool_drafts`
- `review_tool_generations`
### `admin`
### `diretor`
Responsavel por configuracao, publicacao e gestao de acesso.
Responsavel por configuracao, aprovacao, publicacao e gestao de acesso interno.
Permissoes iniciais:
- todas as de `staff`
- todas as de `colaborador`
- `review_tool_generations`
- `publish_tools`
- `manage_settings`
- `manage_staff_accounts`
@ -72,6 +65,153 @@ Ele cobre:
- `PublishedToolContract`
- `ToolPublicationEnvelope`
## Contrato de leitura operacional do produto
O contrato inicial fica em `shared/contracts/product_operational_data.py`.
Ele cobre:
- datasets operacionais que o `admin` pode consultar do `product`
- dominios atuais: `inventory`, `sales`, `review`, `rental`, `conversation`, `integration`
- granularidade inicial de leitura por registro e por agregado
- campos liberados para relatorio e operacao
- campos bloqueados quando carregam identidade do cliente, texto livre ou segredos operacionais
- estrategia de leitura por `admin_read_model`, consistencia eventual e leitura sem query direta do painel no banco operacional do produto
- estrategia de materializacao inicial por `etl_incremental`, persistida em `snapshot_table` e exposta ao painel por `dedicated_view`
### Regra de permissao
A leitura desses datasets nasce sob `view_reports`.
Isso significa:
- `colaborador` pode consultar relatorios e snapshots operacionais
- `diretor` herda essa leitura
- a permissao nao autoriza escrita nem governanca sobre tabelas operacionais
### Regra de minimizacao
Mesmo quando um dataset do produto entra na fronteira compartilhada, o `admin` nao recebe automaticamente todos os seus campos.
A fronteira correta eh:
- expor indicadores operacionais, ids tecnicos e chaves publicas necessarias ao relatorio
- bloquear `cpf`, `email`, `external_id`, payloads brutos, mensagens livres e identificadores sensiveis
- preferir agregados quando o mesmo objetivo nao exigir leitura linha a linha
### Regra de isolamento do hot path
As consultas administrativas de relatorio nao devem ser executadas diretamente sobre as tabelas operacionais do produto a partir de uma request web do painel.
A fronteira correta eh:
- o `product` escreve estado operacional
- uma camada assincrona de `etl_incremental` materializa snapshots sanitizados no admin
- o painel e as APIs administrativas consultam `dedicated_view` construidas sobre esses snapshots
- nenhuma view administrativa pode apontar diretamente para tabelas live do `product`
## Contrato de configuracao funcional governada
O contrato inicial fica em `shared/contracts/system_functional_configuration.py`.
Ele cobre:
- quais configuracoes funcionais o `admin` pode consultar do sistema
- quais configuracoes podem ser alteradas apenas por `diretor`
- a separacao entre runtime de atendimento e runtime de geracao de tools
- a diferenca entre estado governado no `admin` e estado efetivo publicado no `product`
- a proibicao de alterar segredos, infra e tabelas operacionais a partir do painel
### Superficies iniciais declaradas
As primeiras configuracoes funcionais compartilhadas sao:
- `allowed_model_catalog`
- `atendimento_runtime_profile`
- `tool_generation_runtime_profile`
- `bot_behavior_policy`
- `channel_operation_policy`
- `published_runtime_state`
### Regra de permissao
A leitura dessas configuracoes nasce sob `view_system`.
Isso significa:
- `colaborador` pode consultar configuracoes efetivas, catalogos homologados e estado publicado
- `diretor` herda essa leitura
- apenas `diretor` pode alterar configuracoes governadas, usando `manage_settings`
### Regra de governanca
A fronteira correta eh:
- `diretor` altera apenas configuracoes funcionais governadas
- toda alteracao nasce como estado administrativo versionado
- o `product` consome apenas configuracao publicada e aprovada
- o painel nao altera segredos, variaveis de ambiente, credenciais, schema operacional ou comportamento interno sem governanca
### Regra de separacao entre modelos
A escolha de modelo do bot de atendimento e a escolha de modelo para geracao de tools nao devem compartilhar a mesma chave de configuracao.
A fronteira correta eh:
- `atendimento_runtime_profile` governa o modelo que responde ao cliente final
- `tool_generation_runtime_profile` governa o modelo usado para gerar e validar tools
- cada perfil pode evoluir, ser auditado e ser publicado em ritmos diferentes
## Contrato de governanca do bot
O contrato inicial fica em `shared/contracts/bot_governed_configuration.py`.
Ele cobre, em nivel de campo, quais configuracoes do bot de atendimento ficam sob governanca administrativa.
As primeiras superficies governadas sao:
- selecao de modelo do bot: `provider`, `model_name`
- geracao de resposta: `temperature`, `max_output_tokens`, `prompt_profile_ref`
- uso de tools: `tool_policy_ref`, `max_tool_calls_per_turn`, `confirmation_policy`
- fallback e handoff: `fallback_mode`, `handoff_enabled`, `handoff_intents`
- operacao por canal: `enabled`, `maintenance_mode`, `default_route`, `operation_window_ref`
### Regra de permissao
A leitura dessas configuracoes continua sob `view_system`.
A alteracao governada continua restrita a `diretor`, com `manage_settings`.
### Regra de fronteira
Esse contrato deixa explicito que:
- runtime de geracao de tools nao entra como configuracao do bot de atendimento
- o painel governa referencias e politicas funcionais, nao segredos nem infraestrutura
- nenhuma configuracao do bot e aplicada por escrita direta no banco ou runtime live do `product`
- toda mudanca passa por publicacao versionada e auditavel
## Contrato de separacao entre runtimes de modelo
O contrato inicial fica em `shared/contracts/model_runtime_separation.py`.
Ele cobre:
- os alvos `atendimento` e `tool_generation`
- a `config_key` de cada runtime
- o servico consumidor de cada perfil de modelo
- a exigencia de publicacao e rollback independentes
- a proibicao de propagacao implicita entre os dois runtimes
### Regra de fronteira
Esse contrato deixa explicito que:
- o runtime de atendimento e consumido pelo `product`
- o runtime de geracao e consumido pelo `admin`
- trocar um perfil nao troca automaticamente o outro
- os dois podem compartilhar um provedor homologado, mas nao compartilham estado de configuracao
## Como isso sera usado depois
### No `orquestrador-admin`
@ -80,16 +220,23 @@ Ele cobre:
- associar `StaffAccount.role`
- controlar acesso a UI, as rotas e a aprovacao de tools
- emitir `ToolPublicationEnvelope` quando uma tool for publicada
- construir relatorios usando apenas datasets declarados no contrato compartilhado
- consultar read models administrativos em vez de tabelas live do produto
- governar configuracoes funcionais sem escrever diretamente no banco operacional do `product`
### No `orquestrador-product`
- consumir apenas tools publicadas
- validar status e versao do contrato recebido
- expor fronteiras seguras para sincronizacao incremental dos datasets permitidos ao `admin`
- consumir apenas configuracoes funcionais publicadas e aprovadas
- evitar dependencia do runtime do admin no hot path
## Proximos passos naturais
- criar a entidade `StaffAccount`
- plugar a role do usuario interno ao contrato compartilhado
- modelar a persistencia de drafts/publicacoes de tool
- criar rotas administrativas para relatorios
- criar rotas administrativas para configuracao funcional do sistema
- estruturar snapshots e views de vendas, arrecadacao e operacao
- manter a escrita administrativa fora das tabelas operacionais do produto

@ -13,15 +13,46 @@ Nesta fase, os primeiros contratos compartilhados sao:
- `access_control.py`
- define a hierarquia inicial de acesso interno
- papeis: `viewer`, `staff`, `admin`
- permissoes iniciais para relatorios, configuracao, revisao e publicacao
- papeis: `colaborador`, `diretor`
- `colaborador` consulta o fluxo operacional e cadastra novas tools em draft
- `diretor` revisa, aprova, publica tools e cadastra novos colaboradores
- `tool_publication.py`
- define o contrato minimo de publicacao de tools do `admin` para o `product`
- inclui envelope de publicacao, status de ciclo de vida e schema de parametros
- `product_operational_data.py`
- define quais datasets operacionais do `product` podem ser consultados pelo `admin`
- explicita dominios, granularidade de leitura, campos permitidos e campos bloqueados
- reforca que o acesso administrativo nasce como leitura orientada a relatorios
- declara que a leitura deve acontecer por `admin_read_model`, com consistencia eventual e sem query direta do painel no banco operacional do produto
- formaliza a materializacao inicial por `etl_incremental` em `snapshot_table`, servida por `dedicated_view`
- deixa explicito que a fase inicial nao usa replica operacional do produto para abrir dashboards administrativos
- `system_functional_configuration.py`
- define quais configuracoes funcionais o `admin` pode consultar e quais podem ser alteradas
- separa o runtime do bot de atendimento do runtime de geracao de tools
- estabelece catalogo homologado de modelos, politicas do bot, politicas de canal e estado efetivo publicado
- reforca que apenas `diretor` altera configuracoes governadas com `manage_settings`
- deixa explicito que o painel nao altera segredos, credenciais ou tabelas operacionais do produto
- `bot_governed_configuration.py`
- detalha quais campos do bot ficam sob governanca administrativa
- cobre selecao de modelo, geracao de resposta, uso de tools, fallback, handoff e operacao por canal
- deixa explicito que a governanca do bot usa publicacao versionada e nao escrita direta no runtime do produto
- reforca que runtime de geracao de tools nao e configuracao do bot de atendimento
- `model_runtime_separation.py`
- formaliza que atendimento e geracao de tools usam perfis de modelo distintos
- separa config key, catalogo alvo, publicacao e rollback entre os dois runtimes
- deixa explicito que uma mudanca em um runtime nao propaga automaticamente para o outro
## Regras
- `shared/contracts` deve guardar apenas contratos estaveis entre servicos
- nada aqui deve importar modulos internos de `app/` ou `admin_app/`
- as mudancas devem ser additive-first para permitir deploy independente entre `product` e `admin`
- contratos de leitura operacional nao autorizam escrita administrativa nas tabelas do produto
- relatorios administrativos devem consumir read models assincronos, nunca scans pesados no hot path do atendimento
- views dedicadas de relatorio so podem ser construidas sobre snapshots sanitizados do admin, nunca sobre tabelas live do produto
- configuracoes funcionais governadas nao autorizam escrita direta no runtime do `product` durante request web do painel

@ -3,10 +3,52 @@
from shared.contracts.access_control import (
AdminPermission,
StaffRole,
normalize_staff_role,
permissions_for_role,
role_has_permission,
role_includes,
)
from shared.contracts.bot_governed_configuration import (
BOT_GOVERNED_SETTINGS,
BotGovernanceArea,
BotGovernanceMutability,
BotGovernedSettingContract,
get_bot_governed_setting,
)
from shared.contracts.model_runtime_separation import (
MODEL_RUNTIME_PROFILES,
MODEL_RUNTIME_SEPARATION_RULES,
ModelRuntimePurpose,
ModelRuntimeSeparationContract,
ModelRuntimeSeparationRule,
ModelRuntimeTarget,
get_model_runtime_contract,
)
from shared.contracts.product_operational_data import (
PRODUCT_OPERATIONAL_DATASETS,
OperationalConsistencyModel,
OperationalDataDomain,
OperationalDataSensitivity,
OperationalDatasetContract,
OperationalFieldContract,
OperationalFreshnessTarget,
OperationalQuerySurface,
OperationalReadGranularity,
OperationalReadModel,
OperationalStorageShape,
OperationalSyncStrategy,
get_operational_dataset,
)
from shared.contracts.system_functional_configuration import (
SYSTEM_FUNCTIONAL_CONFIGURATIONS,
FunctionalConfigurationContract,
FunctionalConfigurationDomain,
FunctionalConfigurationFieldContract,
FunctionalConfigurationMutability,
FunctionalConfigurationPropagation,
FunctionalConfigurationSource,
get_functional_configuration,
)
from shared.contracts.tool_publication import (
PublishedToolContract,
ServiceName,
@ -18,13 +60,47 @@ from shared.contracts.tool_publication import (
__all__ = [
"AdminPermission",
"BOT_GOVERNED_SETTINGS",
"MODEL_RUNTIME_PROFILES",
"MODEL_RUNTIME_SEPARATION_RULES",
"PRODUCT_OPERATIONAL_DATASETS",
"PublishedToolContract",
"SYSTEM_FUNCTIONAL_CONFIGURATIONS",
"ServiceName",
"StaffRole",
"ToolLifecycleStatus",
"ToolParameterContract",
"ToolParameterType",
"ToolPublicationEnvelope",
"BotGovernanceArea",
"BotGovernanceMutability",
"BotGovernedSettingContract",
"ModelRuntimePurpose",
"ModelRuntimeSeparationContract",
"ModelRuntimeSeparationRule",
"ModelRuntimeTarget",
"OperationalConsistencyModel",
"OperationalDataDomain",
"OperationalDataSensitivity",
"OperationalDatasetContract",
"OperationalFieldContract",
"OperationalFreshnessTarget",
"OperationalQuerySurface",
"OperationalReadGranularity",
"OperationalReadModel",
"OperationalStorageShape",
"OperationalSyncStrategy",
"FunctionalConfigurationContract",
"FunctionalConfigurationDomain",
"FunctionalConfigurationFieldContract",
"FunctionalConfigurationMutability",
"FunctionalConfigurationPropagation",
"FunctionalConfigurationSource",
"get_bot_governed_setting",
"get_functional_configuration",
"get_model_runtime_contract",
"get_operational_dataset",
"normalize_staff_role",
"permissions_for_role",
"role_has_permission",
"role_includes",

@ -0,0 +1,151 @@
"""Define quais configuracoes do bot ficam sob governanca administrativa."""
from __future__ import annotations
from enum import Enum
from pydantic import BaseModel
from shared.contracts.access_control import AdminPermission
class BotGovernanceArea(str, Enum):
MODEL_SELECTION = "model_selection"
RESPONSE_GENERATION = "response_generation"
TOOL_USAGE = "tool_usage"
FALLBACK_AND_HANDOFF = "fallback_and_handoff"
CHANNEL_OPERATION = "channel_operation"
class BotGovernanceMutability(str, Enum):
DIRECTOR_GOVERNED = "director_governed"
class BotGovernedSettingContract(BaseModel):
setting_key: str
parent_config_key: str
field_name: str
area: BotGovernanceArea
description: str
read_permission: AdminPermission = AdminPermission.VIEW_SYSTEM
write_permission: AdminPermission = AdminPermission.MANAGE_SETTINGS
mutability: BotGovernanceMutability = BotGovernanceMutability.DIRECTOR_GOVERNED
versioned_publication_required: bool = True
direct_product_write_allowed: bool = False
BOT_GOVERNED_SETTINGS: tuple[BotGovernedSettingContract, ...] = (
BotGovernedSettingContract(
setting_key="bot_model_provider",
parent_config_key="atendimento_runtime_profile",
field_name="provider",
area=BotGovernanceArea.MODEL_SELECTION,
description="Provedor do modelo usado pelo bot de atendimento.",
),
BotGovernedSettingContract(
setting_key="bot_model_name",
parent_config_key="atendimento_runtime_profile",
field_name="model_name",
area=BotGovernanceArea.MODEL_SELECTION,
description="Modelo selecionado para responder ao cliente final.",
),
BotGovernedSettingContract(
setting_key="bot_temperature",
parent_config_key="atendimento_runtime_profile",
field_name="temperature",
area=BotGovernanceArea.RESPONSE_GENERATION,
description="Temperatura aplicada nas respostas do bot.",
),
BotGovernedSettingContract(
setting_key="bot_max_output_tokens",
parent_config_key="atendimento_runtime_profile",
field_name="max_output_tokens",
area=BotGovernanceArea.RESPONSE_GENERATION,
description="Limite de saida usado no runtime de atendimento.",
),
BotGovernedSettingContract(
setting_key="bot_prompt_profile_ref",
parent_config_key="atendimento_runtime_profile",
field_name="prompt_profile_ref",
area=BotGovernanceArea.RESPONSE_GENERATION,
description="Referencia do perfil de prompt publicado para o bot.",
),
BotGovernedSettingContract(
setting_key="bot_tool_policy_ref",
parent_config_key="atendimento_runtime_profile",
field_name="tool_policy_ref",
area=BotGovernanceArea.TOOL_USAGE,
description="Referencia da politica de uso de tools pelo bot.",
),
BotGovernedSettingContract(
setting_key="bot_fallback_mode",
parent_config_key="bot_behavior_policy",
field_name="fallback_mode",
area=BotGovernanceArea.FALLBACK_AND_HANDOFF,
description="Modo funcional de fallback quando o bot nao conclui a tarefa.",
),
BotGovernedSettingContract(
setting_key="bot_handoff_enabled",
parent_config_key="bot_behavior_policy",
field_name="handoff_enabled",
area=BotGovernanceArea.FALLBACK_AND_HANDOFF,
description="Habilita o encaminhamento para atendimento humano.",
),
BotGovernedSettingContract(
setting_key="bot_handoff_intents",
parent_config_key="bot_behavior_policy",
field_name="handoff_intents",
area=BotGovernanceArea.FALLBACK_AND_HANDOFF,
description="Lista de intencoes que exigem handoff humano.",
),
BotGovernedSettingContract(
setting_key="bot_max_tool_calls_per_turn",
parent_config_key="bot_behavior_policy",
field_name="max_tool_calls_per_turn",
area=BotGovernanceArea.TOOL_USAGE,
description="Limite de chamadas de tools por turno conversacional.",
),
BotGovernedSettingContract(
setting_key="bot_confirmation_policy",
parent_config_key="bot_behavior_policy",
field_name="confirmation_policy",
area=BotGovernanceArea.TOOL_USAGE,
description="Politica de confirmacao antes de acao critica no fluxo.",
),
BotGovernedSettingContract(
setting_key="channel_enabled",
parent_config_key="channel_operation_policy",
field_name="enabled",
area=BotGovernanceArea.CHANNEL_OPERATION,
description="Habilita ou desabilita o bot em um canal homologado.",
),
BotGovernedSettingContract(
setting_key="channel_maintenance_mode",
parent_config_key="channel_operation_policy",
field_name="maintenance_mode",
area=BotGovernanceArea.CHANNEL_OPERATION,
description="Liga manutencao controlada em um canal do bot.",
),
BotGovernedSettingContract(
setting_key="channel_default_route",
parent_config_key="channel_operation_policy",
field_name="default_route",
area=BotGovernanceArea.CHANNEL_OPERATION,
description="Define a rota funcional padrao por canal.",
),
BotGovernedSettingContract(
setting_key="channel_operation_window_ref",
parent_config_key="channel_operation_policy",
field_name="operation_window_ref",
area=BotGovernanceArea.CHANNEL_OPERATION,
description="Referencia a janela operacional aplicada por canal.",
),
)
def get_bot_governed_setting(setting_key: str) -> BotGovernedSettingContract | None:
normalized = str(setting_key or "").strip().lower()
for setting in BOT_GOVERNED_SETTINGS:
if setting.setting_key == normalized:
return setting
return None

@ -0,0 +1,85 @@
"""Define a separacao entre runtime de atendimento e runtime de geracao de tools."""
from __future__ import annotations
from enum import Enum
from pydantic import BaseModel
from shared.contracts.access_control import AdminPermission
from shared.contracts.tool_publication import ServiceName
class ModelRuntimeTarget(str, Enum):
ATENDIMENTO = "atendimento"
TOOL_GENERATION = "tool_generation"
class ModelRuntimePurpose(str, Enum):
CUSTOMER_RESPONSE = "customer_response"
TOOL_GENERATION_AND_VALIDATION = "tool_generation_and_validation"
class ModelRuntimeSeparationRule(str, Enum):
SEPARATE_CONFIG_KEYS = "separate_config_keys"
SEPARATE_CATALOG_TARGETS = "separate_catalog_targets"
INDEPENDENT_PUBLICATION = "independent_publication"
INDEPENDENT_ROLLBACK = "independent_rollback"
NO_IMPLICIT_PROPAGATION = "no_implicit_propagation"
class ModelRuntimeSeparationContract(BaseModel):
runtime_target: ModelRuntimeTarget
config_key: str
catalog_runtime_target: ModelRuntimeTarget
purpose: ModelRuntimePurpose
consumed_by_service: ServiceName
description: str
read_permission: AdminPermission = AdminPermission.VIEW_SYSTEM
write_permission: AdminPermission = AdminPermission.MANAGE_SETTINGS
published_independently: bool = True
rollback_independently: bool = True
cross_target_propagation_allowed: bool = False
affects_customer_response: bool = False
can_generate_code: bool = False
MODEL_RUNTIME_PROFILES: tuple[ModelRuntimeSeparationContract, ...] = (
ModelRuntimeSeparationContract(
runtime_target=ModelRuntimeTarget.ATENDIMENTO,
config_key="atendimento_runtime_profile",
catalog_runtime_target=ModelRuntimeTarget.ATENDIMENTO,
purpose=ModelRuntimePurpose.CUSTOMER_RESPONSE,
consumed_by_service=ServiceName.PRODUCT,
description="Runtime do modelo que responde ao cliente final no fluxo de atendimento.",
affects_customer_response=True,
can_generate_code=False,
),
ModelRuntimeSeparationContract(
runtime_target=ModelRuntimeTarget.TOOL_GENERATION,
config_key="tool_generation_runtime_profile",
catalog_runtime_target=ModelRuntimeTarget.TOOL_GENERATION,
purpose=ModelRuntimePurpose.TOOL_GENERATION_AND_VALIDATION,
consumed_by_service=ServiceName.ADMIN,
description="Runtime do modelo usado para gerar e validar novas tools no contexto administrativo.",
affects_customer_response=False,
can_generate_code=True,
),
)
MODEL_RUNTIME_SEPARATION_RULES: tuple[ModelRuntimeSeparationRule, ...] = (
ModelRuntimeSeparationRule.SEPARATE_CONFIG_KEYS,
ModelRuntimeSeparationRule.SEPARATE_CATALOG_TARGETS,
ModelRuntimeSeparationRule.INDEPENDENT_PUBLICATION,
ModelRuntimeSeparationRule.INDEPENDENT_ROLLBACK,
ModelRuntimeSeparationRule.NO_IMPLICIT_PROPAGATION,
)
def get_model_runtime_contract(runtime_target: ModelRuntimeTarget | str) -> ModelRuntimeSeparationContract | None:
normalized = str(runtime_target or "").strip().lower()
for runtime_contract in MODEL_RUNTIME_PROFILES:
if runtime_contract.runtime_target.value == normalized:
return runtime_contract
return None

@ -0,0 +1,375 @@
"""Define o escopo de leitura operacional do admin sobre o servico de produto."""
from __future__ import annotations
from enum import Enum
from pydantic import BaseModel, Field
from shared.contracts.access_control import AdminPermission
class OperationalDataDomain(str, Enum):
INVENTORY = "inventory"
SALES = "sales"
REVIEW = "review"
RENTAL = "rental"
CONVERSATION = "conversation"
INTEGRATION = "integration"
class OperationalDataSensitivity(str, Enum):
OPERATIONAL = "operational"
INTERNAL_IDENTIFIER = "internal_identifier"
CUSTOMER_IDENTIFIER = "customer_identifier"
FREE_TEXT = "free_text"
SECRET = "secret"
class OperationalReadGranularity(str, Enum):
AGGREGATE = "aggregate"
RECORD = "record"
class OperationalReadModel(str, Enum):
ADMIN_READ_MODEL = "admin_read_model"
class OperationalConsistencyModel(str, Enum):
EVENTUAL = "eventual"
class OperationalFreshnessTarget(str, Enum):
NEAR_REAL_TIME = "near_real_time"
INTRA_HOUR = "intra_hour"
INTRA_DAY = "intra_day"
class OperationalSyncStrategy(str, Enum):
ETL_INCREMENTAL = "etl_incremental"
class OperationalStorageShape(str, Enum):
SNAPSHOT_TABLE = "snapshot_table"
class OperationalQuerySurface(str, Enum):
DEDICATED_VIEW = "dedicated_view"
class OperationalFieldContract(BaseModel):
name: str
description: str
sensitivity: OperationalDataSensitivity = OperationalDataSensitivity.OPERATIONAL
class OperationalDatasetContract(BaseModel):
dataset_key: str
domain: OperationalDataDomain
description: str
source_table: str
read_permission: AdminPermission = AdminPermission.VIEW_REPORTS
report_read_model: OperationalReadModel = OperationalReadModel.ADMIN_READ_MODEL
consistency_model: OperationalConsistencyModel = OperationalConsistencyModel.EVENTUAL
sync_strategy: OperationalSyncStrategy = OperationalSyncStrategy.ETL_INCREMENTAL
storage_shape: OperationalStorageShape = OperationalStorageShape.SNAPSHOT_TABLE
query_surface: OperationalQuerySurface = OperationalQuerySurface.DEDICATED_VIEW
uses_product_replica: bool = False
direct_product_query_allowed: bool = False
freshness_target: OperationalFreshnessTarget = OperationalFreshnessTarget.INTRA_HOUR
allowed_granularities: tuple[OperationalReadGranularity, ...] = Field(
default=(
OperationalReadGranularity.AGGREGATE,
OperationalReadGranularity.RECORD,
)
)
write_allowed: bool = False
allowed_fields: tuple[OperationalFieldContract, ...]
blocked_fields: tuple[OperationalFieldContract, ...] = Field(default_factory=tuple)
PRODUCT_OPERATIONAL_DATASETS: tuple[OperationalDatasetContract, ...] = (
OperationalDatasetContract(
dataset_key="vehicle_inventory",
domain=OperationalDataDomain.INVENTORY,
description="Estoque operacional de veiculos disponiveis para atendimento comercial.",
source_table="vehicles",
freshness_target=OperationalFreshnessTarget.INTRA_HOUR,
allowed_fields=(
OperationalFieldContract(name="id", description="Identificador tecnico do veiculo."),
OperationalFieldContract(name="modelo", description="Modelo comercial do veiculo."),
OperationalFieldContract(name="categoria", description="Categoria comercial do veiculo."),
OperationalFieldContract(name="preco", description="Preco anunciado no estoque."),
OperationalFieldContract(name="created_at", description="Data de entrada do registro no estoque."),
),
),
OperationalDatasetContract(
dataset_key="sales_orders",
domain=OperationalDataDomain.SALES,
description="Pedidos de venda usados para operacao, conversao e cancelamentos.",
source_table="orders",
freshness_target=OperationalFreshnessTarget.NEAR_REAL_TIME,
allowed_fields=(
OperationalFieldContract(name="numero_pedido", description="Numero publico do pedido."),
OperationalFieldContract(name="vehicle_id", description="Veiculo associado ao pedido."),
OperationalFieldContract(name="modelo_veiculo", description="Modelo comercial reservado no pedido."),
OperationalFieldContract(name="valor_veiculo", description="Valor negociado do veiculo."),
OperationalFieldContract(name="status", description="Status operacional do pedido."),
OperationalFieldContract(name="motivo_cancelamento", description="Motivo operacional do cancelamento."),
OperationalFieldContract(name="data_cancelamento", description="Momento em que o pedido foi cancelado."),
OperationalFieldContract(name="created_at", description="Data de criacao do pedido."),
OperationalFieldContract(name="updated_at", description="Data da ultima atualizacao do pedido."),
),
blocked_fields=(
OperationalFieldContract(
name="user_id",
description="Identificador interno do usuario final no produto.",
sensitivity=OperationalDataSensitivity.INTERNAL_IDENTIFIER,
),
OperationalFieldContract(
name="cpf",
description="Identificador civil do cliente final.",
sensitivity=OperationalDataSensitivity.CUSTOMER_IDENTIFIER,
),
),
),
OperationalDatasetContract(
dataset_key="review_schedules",
domain=OperationalDataDomain.REVIEW,
description="Agenda operacional de revisoes e disponibilidade de slots.",
source_table="review_schedules",
freshness_target=OperationalFreshnessTarget.NEAR_REAL_TIME,
allowed_fields=(
OperationalFieldContract(name="protocolo", description="Protocolo publico do agendamento."),
OperationalFieldContract(name="placa", description="Placa do veiculo agendado."),
OperationalFieldContract(name="data_hora", description="Data e hora do slot de revisao."),
OperationalFieldContract(name="status", description="Status operacional do agendamento."),
OperationalFieldContract(name="created_at", description="Data de criacao do agendamento."),
),
blocked_fields=(
OperationalFieldContract(
name="user_id",
description="Identificador interno do usuario final no produto.",
sensitivity=OperationalDataSensitivity.INTERNAL_IDENTIFIER,
),
),
),
OperationalDatasetContract(
dataset_key="rental_fleet",
domain=OperationalDataDomain.RENTAL,
description="Frota operacional de locacao disponivel para consulta administrativa.",
source_table="rental_vehicles",
freshness_target=OperationalFreshnessTarget.NEAR_REAL_TIME,
allowed_fields=(
OperationalFieldContract(name="id", description="Identificador tecnico do veiculo de locacao."),
OperationalFieldContract(name="placa", description="Placa do veiculo de locacao."),
OperationalFieldContract(name="modelo", description="Modelo do veiculo de locacao."),
OperationalFieldContract(name="categoria", description="Categoria comercial da locacao."),
OperationalFieldContract(name="ano", description="Ano de fabricacao do veiculo."),
OperationalFieldContract(name="valor_diaria", description="Valor de diaria vigente."),
OperationalFieldContract(name="status", description="Status operacional do veiculo na frota."),
OperationalFieldContract(name="created_at", description="Data de cadastro do veiculo na frota."),
),
),
OperationalDatasetContract(
dataset_key="rental_contracts",
domain=OperationalDataDomain.RENTAL,
description="Contratos de locacao usados para operacao, retorno e inadimplencia.",
source_table="rental_contracts",
freshness_target=OperationalFreshnessTarget.NEAR_REAL_TIME,
allowed_fields=(
OperationalFieldContract(name="contrato_numero", description="Numero publico do contrato."),
OperationalFieldContract(name="rental_vehicle_id", description="Identificador tecnico do veiculo locado."),
OperationalFieldContract(name="placa", description="Placa do veiculo vinculado ao contrato."),
OperationalFieldContract(name="modelo_veiculo", description="Modelo do veiculo locado."),
OperationalFieldContract(name="categoria", description="Categoria da locacao."),
OperationalFieldContract(name="data_inicio", description="Inicio da locacao."),
OperationalFieldContract(name="data_fim_prevista", description="Fim previsto da locacao."),
OperationalFieldContract(name="data_devolucao", description="Momento efetivo da devolucao."),
OperationalFieldContract(name="valor_diaria", description="Valor unitario da diaria."),
OperationalFieldContract(name="valor_previsto", description="Valor previsto ao abrir o contrato."),
OperationalFieldContract(name="valor_final", description="Valor final consolidado da locacao."),
OperationalFieldContract(name="status", description="Status operacional do contrato."),
OperationalFieldContract(name="created_at", description="Data de criacao do contrato."),
OperationalFieldContract(name="updated_at", description="Data da ultima atualizacao do contrato."),
),
blocked_fields=(
OperationalFieldContract(
name="user_id",
description="Identificador interno do usuario final no produto.",
sensitivity=OperationalDataSensitivity.INTERNAL_IDENTIFIER,
),
OperationalFieldContract(
name="cpf",
description="Identificador civil do cliente final.",
sensitivity=OperationalDataSensitivity.CUSTOMER_IDENTIFIER,
),
OperationalFieldContract(
name="observacoes",
description="Campo livre informado durante a operacao de locacao.",
sensitivity=OperationalDataSensitivity.FREE_TEXT,
),
),
),
OperationalDatasetContract(
dataset_key="rental_payments",
domain=OperationalDataDomain.RENTAL,
description="Pagamentos de locacao usados para arrecadacao e conciliacao operacional.",
source_table="rental_payments",
freshness_target=OperationalFreshnessTarget.NEAR_REAL_TIME,
allowed_fields=(
OperationalFieldContract(name="protocolo", description="Protocolo publico do pagamento."),
OperationalFieldContract(name="contrato_numero", description="Contrato associado ao pagamento."),
OperationalFieldContract(name="placa", description="Placa vinculada ao contrato pago."),
OperationalFieldContract(name="valor", description="Valor liquidado no pagamento."),
OperationalFieldContract(name="data_pagamento", description="Momento do pagamento."),
OperationalFieldContract(name="created_at", description="Data de registro do pagamento."),
),
blocked_fields=(
OperationalFieldContract(
name="user_id",
description="Identificador interno do usuario final no produto.",
sensitivity=OperationalDataSensitivity.INTERNAL_IDENTIFIER,
),
OperationalFieldContract(
name="rental_contract_id",
description="Chave tecnica interna do contrato no banco operacional.",
sensitivity=OperationalDataSensitivity.INTERNAL_IDENTIFIER,
),
OperationalFieldContract(
name="favorecido",
description="Nome textual do favorecido no comprovante.",
sensitivity=OperationalDataSensitivity.FREE_TEXT,
),
OperationalFieldContract(
name="identificador_comprovante",
description="Identificador do comprovante de pagamento.",
sensitivity=OperationalDataSensitivity.SECRET,
),
OperationalFieldContract(
name="observacoes",
description="Campo livre informado durante o pagamento.",
sensitivity=OperationalDataSensitivity.FREE_TEXT,
),
),
),
OperationalDatasetContract(
dataset_key="conversation_turns",
domain=OperationalDataDomain.CONVERSATION,
description="Telemetria operacional das conversas para eficiencia, erro e uso de tools.",
source_table="conversation_turns",
freshness_target=OperationalFreshnessTarget.INTRA_HOUR,
allowed_fields=(
OperationalFieldContract(name="request_id", description="Identificador tecnico do turno processado."),
OperationalFieldContract(name="conversation_id", description="Identificador tecnico da conversa."),
OperationalFieldContract(name="channel", description="Canal do atendimento."),
OperationalFieldContract(name="turn_status", description="Status do turno conversacional."),
OperationalFieldContract(name="intent", description="Intencao classificada para o turno."),
OperationalFieldContract(name="domain", description="Dominio operacional associado ao turno."),
OperationalFieldContract(name="action", description="Acao tomada pelo orquestrador."),
OperationalFieldContract(name="tool_name", description="Tool chamada durante o turno."),
OperationalFieldContract(name="elapsed_ms", description="Tempo de processamento do turno em milissegundos."),
OperationalFieldContract(name="started_at", description="Inicio do processamento do turno."),
OperationalFieldContract(name="completed_at", description="Fim do processamento do turno."),
),
blocked_fields=(
OperationalFieldContract(
name="user_id",
description="Identificador interno do usuario final no produto.",
sensitivity=OperationalDataSensitivity.INTERNAL_IDENTIFIER,
),
OperationalFieldContract(
name="external_id",
description="Identificador externo do usuario final no canal.",
sensitivity=OperationalDataSensitivity.CUSTOMER_IDENTIFIER,
),
OperationalFieldContract(
name="username",
description="Username do usuario final no canal.",
sensitivity=OperationalDataSensitivity.CUSTOMER_IDENTIFIER,
),
OperationalFieldContract(
name="user_message",
description="Mensagem original do usuario final.",
sensitivity=OperationalDataSensitivity.FREE_TEXT,
),
OperationalFieldContract(
name="assistant_response",
description="Resposta textual completa enviada ao usuario final.",
sensitivity=OperationalDataSensitivity.FREE_TEXT,
),
OperationalFieldContract(
name="tool_arguments",
description="Payload bruto dos argumentos enviados para tools.",
sensitivity=OperationalDataSensitivity.FREE_TEXT,
),
OperationalFieldContract(
name="error_detail",
description="Detalhe bruto de erro que pode carregar contexto sensivel.",
sensitivity=OperationalDataSensitivity.FREE_TEXT,
),
),
),
OperationalDatasetContract(
dataset_key="integration_deliveries",
domain=OperationalDataDomain.INTEGRATION,
description="Entrega operacional de eventos para provedores externos e observabilidade de falhas.",
source_table="integration_deliveries",
freshness_target=OperationalFreshnessTarget.INTRA_HOUR,
allowed_fields=(
OperationalFieldContract(name="route_id", description="Rota interna que originou a entrega."),
OperationalFieldContract(name="event_type", description="Tipo de evento entregue."),
OperationalFieldContract(name="provider", description="Provedor de integracao usado na entrega."),
OperationalFieldContract(name="status", description="Status atual da entrega."),
OperationalFieldContract(name="attempts", description="Quantidade de tentativas realizadas."),
OperationalFieldContract(name="dispatched_at", description="Momento do disparo da entrega."),
OperationalFieldContract(name="created_at", description="Data de criacao do registro de entrega."),
OperationalFieldContract(name="updated_at", description="Data da ultima atualizacao da entrega."),
),
blocked_fields=(
OperationalFieldContract(
name="payload_json",
description="Payload bruto do evento entregue.",
sensitivity=OperationalDataSensitivity.FREE_TEXT,
),
OperationalFieldContract(
name="recipient_email",
description="Email do destinatario final da integracao.",
sensitivity=OperationalDataSensitivity.CUSTOMER_IDENTIFIER,
),
OperationalFieldContract(
name="recipient_name",
description="Nome do destinatario final da integracao.",
sensitivity=OperationalDataSensitivity.CUSTOMER_IDENTIFIER,
),
OperationalFieldContract(
name="rendered_subject",
description="Assunto renderizado da mensagem enviada.",
sensitivity=OperationalDataSensitivity.FREE_TEXT,
),
OperationalFieldContract(
name="rendered_body",
description="Corpo renderizado da mensagem enviada.",
sensitivity=OperationalDataSensitivity.FREE_TEXT,
),
OperationalFieldContract(
name="provider_message_id",
description="Identificador bruto devolvido pelo provedor externo.",
sensitivity=OperationalDataSensitivity.SECRET,
),
OperationalFieldContract(
name="last_error",
description="Detalhe textual do ultimo erro de entrega.",
sensitivity=OperationalDataSensitivity.FREE_TEXT,
),
),
),
)
def get_operational_dataset(dataset_key: str) -> OperationalDatasetContract | None:
normalized = str(dataset_key or "").strip().lower()
for dataset in PRODUCT_OPERATIONAL_DATASETS:
if dataset.dataset_key == normalized:
return dataset
return None

@ -0,0 +1,258 @@
"""Define o escopo de configuracao funcional governada entre admin e product."""
from __future__ import annotations
from enum import Enum
from pydantic import BaseModel
from shared.contracts.access_control import AdminPermission
class FunctionalConfigurationDomain(str, Enum):
MODEL_CATALOG = "model_catalog"
ATENDIMENTO_RUNTIME = "atendimento_runtime"
TOOL_GENERATION_RUNTIME = "tool_generation_runtime"
BOT_POLICY = "bot_policy"
CHANNEL_OPERATION = "channel_operation"
CONFIG_PUBLICATION = "config_publication"
class FunctionalConfigurationMutability(str, Enum):
READ_ONLY = "read_only"
DIRECTOR_GOVERNED = "director_governed"
class FunctionalConfigurationSource(str, Enum):
PLATFORM_CATALOG = "platform_catalog"
ADMIN_GOVERNED_STATE = "admin_governed_state"
PRODUCT_EFFECTIVE_STATE = "product_effective_state"
class FunctionalConfigurationPropagation(str, Enum):
OBSERVATION_ONLY = "observation_only"
VERSIONED_PUBLICATION = "versioned_publication"
class FunctionalConfigurationFieldContract(BaseModel):
name: str
description: str
writable: bool = True
secret: bool = False
class FunctionalConfigurationContract(BaseModel):
config_key: str
domain: FunctionalConfigurationDomain
description: str
source: FunctionalConfigurationSource
read_permission: AdminPermission = AdminPermission.VIEW_SYSTEM
write_permission: AdminPermission | None = AdminPermission.MANAGE_SETTINGS
mutability: FunctionalConfigurationMutability = FunctionalConfigurationMutability.DIRECTOR_GOVERNED
propagation: FunctionalConfigurationPropagation = (
FunctionalConfigurationPropagation.VERSIONED_PUBLICATION
)
affects_product_runtime: bool = True
direct_product_write_allowed: bool = False
fields: tuple[FunctionalConfigurationFieldContract, ...]
SYSTEM_FUNCTIONAL_CONFIGURATIONS: tuple[FunctionalConfigurationContract, ...] = (
FunctionalConfigurationContract(
config_key="allowed_model_catalog",
domain=FunctionalConfigurationDomain.MODEL_CATALOG,
description="Catalogo de modelos liberados pela plataforma para atendimento e geracao de tools.",
source=FunctionalConfigurationSource.PLATFORM_CATALOG,
write_permission=None,
mutability=FunctionalConfigurationMutability.READ_ONLY,
propagation=FunctionalConfigurationPropagation.OBSERVATION_ONLY,
affects_product_runtime=False,
fields=(
FunctionalConfigurationFieldContract(
name="runtime_target",
description="Destino funcional do modelo, como atendimento ou geracao de tools.",
writable=False,
),
FunctionalConfigurationFieldContract(
name="provider",
description="Provedor homologado para o modelo.",
writable=False,
),
FunctionalConfigurationFieldContract(
name="model_name",
description="Nome tecnico do modelo liberado.",
writable=False,
),
FunctionalConfigurationFieldContract(
name="capability_tags",
description="Capacidades suportadas pelo modelo homologado.",
writable=False,
),
FunctionalConfigurationFieldContract(
name="status",
description="Estado de homologacao do modelo no catalogo da plataforma.",
writable=False,
),
),
),
FunctionalConfigurationContract(
config_key="atendimento_runtime_profile",
domain=FunctionalConfigurationDomain.ATENDIMENTO_RUNTIME,
description="Perfil funcional ativo para o bot de atendimento no servico de produto.",
source=FunctionalConfigurationSource.ADMIN_GOVERNED_STATE,
fields=(
FunctionalConfigurationFieldContract(
name="provider",
description="Provedor selecionado para o atendimento.",
),
FunctionalConfigurationFieldContract(
name="model_name",
description="Modelo selecionado para o atendimento.",
),
FunctionalConfigurationFieldContract(
name="temperature",
description="Temperatura aplicada nas respostas do atendimento.",
),
FunctionalConfigurationFieldContract(
name="max_output_tokens",
description="Limite de saida usado pelo atendimento.",
),
FunctionalConfigurationFieldContract(
name="prompt_profile_ref",
description="Referencia da estrategia de prompt publicada para o atendimento.",
),
FunctionalConfigurationFieldContract(
name="tool_policy_ref",
description="Referencia da politica de uso de tools pelo atendimento.",
),
),
),
FunctionalConfigurationContract(
config_key="tool_generation_runtime_profile",
domain=FunctionalConfigurationDomain.TOOL_GENERATION_RUNTIME,
description="Perfil funcional usado para geracao e validacao automatica de novas tools.",
source=FunctionalConfigurationSource.ADMIN_GOVERNED_STATE,
fields=(
FunctionalConfigurationFieldContract(
name="provider",
description="Provedor selecionado para a geracao de tools.",
),
FunctionalConfigurationFieldContract(
name="model_name",
description="Modelo selecionado para a geracao de tools.",
),
FunctionalConfigurationFieldContract(
name="reasoning_profile",
description="Perfil de raciocinio aprovado para geracao de codigo.",
),
FunctionalConfigurationFieldContract(
name="max_output_tokens",
description="Limite de saida usado na geracao de tools.",
),
FunctionalConfigurationFieldContract(
name="validation_profile_ref",
description="Referencia da politica de validacao automatica de tools.",
),
),
),
FunctionalConfigurationContract(
config_key="bot_behavior_policy",
domain=FunctionalConfigurationDomain.BOT_POLICY,
description="Politicas funcionais do fluxo do bot para fallback, handoff e uso de tools.",
source=FunctionalConfigurationSource.ADMIN_GOVERNED_STATE,
fields=(
FunctionalConfigurationFieldContract(
name="fallback_mode",
description="Modo funcional de fallback quando o bot nao conclui a tarefa.",
),
FunctionalConfigurationFieldContract(
name="handoff_enabled",
description="Sinaliza se o fluxo pode encaminhar para atendimento humano.",
),
FunctionalConfigurationFieldContract(
name="handoff_intents",
description="Lista de intencoes que forcam handoff humano.",
),
FunctionalConfigurationFieldContract(
name="max_tool_calls_per_turn",
description="Limite de chamadas de tools por turno de atendimento.",
),
FunctionalConfigurationFieldContract(
name="confirmation_policy",
description="Politica de confirmacao antes de acao critica no fluxo.",
),
),
),
FunctionalConfigurationContract(
config_key="channel_operation_policy",
domain=FunctionalConfigurationDomain.CHANNEL_OPERATION,
description="Politicas funcionais por canal, incluindo habilitacao, manutencao e janela operacional.",
source=FunctionalConfigurationSource.ADMIN_GOVERNED_STATE,
fields=(
FunctionalConfigurationFieldContract(
name="channel",
description="Canal operacional ao qual a politica se aplica.",
),
FunctionalConfigurationFieldContract(
name="enabled",
description="Indica se o canal esta habilitado para atendimento.",
),
FunctionalConfigurationFieldContract(
name="maintenance_mode",
description="Sinaliza se o canal esta em manutencao controlada.",
),
FunctionalConfigurationFieldContract(
name="default_route",
description="Rota funcional padrao usada pelo canal.",
),
FunctionalConfigurationFieldContract(
name="operation_window_ref",
description="Referencia da janela operacional aplicada ao canal.",
),
),
),
FunctionalConfigurationContract(
config_key="published_runtime_state",
domain=FunctionalConfigurationDomain.CONFIG_PUBLICATION,
description="Estado efetivo publicado no produto para auditoria de versao e aplicacao runtime.",
source=FunctionalConfigurationSource.PRODUCT_EFFECTIVE_STATE,
write_permission=None,
mutability=FunctionalConfigurationMutability.READ_ONLY,
propagation=FunctionalConfigurationPropagation.OBSERVATION_ONLY,
fields=(
FunctionalConfigurationFieldContract(
name="config_scope",
description="Escopo funcional da configuracao publicada.",
writable=False,
),
FunctionalConfigurationFieldContract(
name="active_version",
description="Versao funcional atualmente ativa no produto.",
writable=False,
),
FunctionalConfigurationFieldContract(
name="published_by",
description="Identificador administrativo de quem publicou a configuracao.",
writable=False,
),
FunctionalConfigurationFieldContract(
name="published_at",
description="Momento da ultima publicacao governada.",
writable=False,
),
FunctionalConfigurationFieldContract(
name="applied_at",
description="Momento em que o produto aplicou a configuracao em runtime.",
writable=False,
),
),
),
)
def get_functional_configuration(config_key: str) -> FunctionalConfigurationContract | None:
normalized = str(config_key or "").strip().lower()
for configuration in SYSTEM_FUNCTIONAL_CONFIGURATIONS:
if configuration.config_key == normalized:
return configuration
return None

@ -3,13 +3,41 @@ from datetime import datetime, timezone
from shared.contracts import (
AdminPermission,
BOT_GOVERNED_SETTINGS,
MODEL_RUNTIME_PROFILES,
MODEL_RUNTIME_SEPARATION_RULES,
BotGovernanceArea,
BotGovernanceMutability,
FunctionalConfigurationDomain,
FunctionalConfigurationMutability,
FunctionalConfigurationPropagation,
FunctionalConfigurationSource,
ModelRuntimePurpose,
ModelRuntimeSeparationRule,
ModelRuntimeTarget,
OperationalConsistencyModel,
OperationalDataDomain,
OperationalDataSensitivity,
OperationalFreshnessTarget,
OperationalQuerySurface,
OperationalReadGranularity,
OperationalReadModel,
OperationalStorageShape,
OperationalSyncStrategy,
PRODUCT_OPERATIONAL_DATASETS,
PublishedToolContract,
ServiceName,
StaffRole,
SYSTEM_FUNCTIONAL_CONFIGURATIONS,
ToolLifecycleStatus,
ToolParameterContract,
ToolParameterType,
ToolPublicationEnvelope,
get_bot_governed_setting,
get_functional_configuration,
get_model_runtime_contract,
get_operational_dataset,
normalize_staff_role,
permissions_for_role,
role_has_permission,
role_includes,
@ -18,23 +46,36 @@ from shared.contracts import (
class AccessControlContractTests(unittest.TestCase):
def test_role_hierarchy_is_ordered(self):
self.assertTrue(role_includes(StaffRole.ADMIN, StaffRole.STAFF))
self.assertTrue(role_includes(StaffRole.STAFF, StaffRole.VIEWER))
self.assertFalse(role_includes(StaffRole.VIEWER, StaffRole.ADMIN))
self.assertTrue(role_includes(StaffRole.DIRETOR, StaffRole.COLABORADOR))
self.assertFalse(role_includes(StaffRole.COLABORADOR, StaffRole.DIRETOR))
def test_permissions_are_inherited_by_higher_roles(self):
def test_legacy_role_aliases_are_normalized_to_portuguese_roles(self):
self.assertEqual(normalize_staff_role("viewer"), StaffRole.COLABORADOR)
self.assertEqual(normalize_staff_role("staff"), StaffRole.COLABORADOR)
self.assertEqual(normalize_staff_role("admin"), StaffRole.DIRETOR)
def test_permissions_are_inherited_by_director(self):
self.assertIn(
AdminPermission.VIEW_REPORTS,
permissions_for_role(StaffRole.VIEWER),
permissions_for_role(StaffRole.COLABORADOR),
)
self.assertTrue(
role_has_permission(StaffRole.STAFF, AdminPermission.MANAGE_TOOL_DRAFTS)
role_has_permission(StaffRole.COLABORADOR, AdminPermission.MANAGE_TOOL_DRAFTS)
)
self.assertTrue(
role_has_permission(StaffRole.ADMIN, AdminPermission.MANAGE_SETTINGS)
self.assertFalse(
role_has_permission(StaffRole.COLABORADOR, AdminPermission.REVIEW_TOOL_GENERATIONS)
)
self.assertFalse(
role_has_permission(StaffRole.COLABORADOR, AdminPermission.PUBLISH_TOOLS)
)
self.assertFalse(
role_has_permission(StaffRole.VIEWER, AdminPermission.PUBLISH_TOOLS)
role_has_permission(StaffRole.COLABORADOR, AdminPermission.MANAGE_SETTINGS)
)
self.assertTrue(
role_has_permission(StaffRole.DIRETOR, AdminPermission.MANAGE_SETTINGS)
)
self.assertTrue(
role_has_permission(StaffRole.DIRETOR, AdminPermission.MANAGE_STAFF_ACCOUNTS)
)
@ -58,7 +99,7 @@ class ToolPublicationContractTests(unittest.TestCase):
implementation_callable="run",
checksum="sha256:abc123",
published_at=datetime(2026, 3, 26, 12, 0, tzinfo=timezone.utc),
published_by="staff:1",
published_by="diretor:1",
)
envelope = ToolPublicationEnvelope(
@ -78,5 +119,268 @@ class ToolPublicationContractTests(unittest.TestCase):
)
class ProductOperationalDataContractTests(unittest.TestCase):
def test_catalog_exposes_expected_operational_domains(self):
self.assertEqual(len(PRODUCT_OPERATIONAL_DATASETS), 8)
self.assertEqual(
{dataset.domain for dataset in PRODUCT_OPERATIONAL_DATASETS},
{
OperationalDataDomain.INVENTORY,
OperationalDataDomain.SALES,
OperationalDataDomain.REVIEW,
OperationalDataDomain.RENTAL,
OperationalDataDomain.CONVERSATION,
OperationalDataDomain.INTEGRATION,
},
)
def test_all_datasets_use_async_admin_read_model_without_direct_product_query(self):
for dataset in PRODUCT_OPERATIONAL_DATASETS:
self.assertEqual(dataset.read_permission, AdminPermission.VIEW_REPORTS)
self.assertEqual(dataset.report_read_model, OperationalReadModel.ADMIN_READ_MODEL)
self.assertEqual(dataset.consistency_model, OperationalConsistencyModel.EVENTUAL)
self.assertEqual(dataset.sync_strategy, OperationalSyncStrategy.ETL_INCREMENTAL)
self.assertEqual(dataset.storage_shape, OperationalStorageShape.SNAPSHOT_TABLE)
self.assertEqual(dataset.query_surface, OperationalQuerySurface.DEDICATED_VIEW)
self.assertFalse(dataset.uses_product_replica)
self.assertFalse(dataset.direct_product_query_allowed)
self.assertFalse(dataset.write_allowed)
def test_sales_orders_dataset_is_read_only_and_blocks_customer_identity(self):
dataset = get_operational_dataset("sales_orders")
self.assertIsNotNone(dataset)
self.assertEqual(
dataset.freshness_target,
OperationalFreshnessTarget.NEAR_REAL_TIME,
)
self.assertEqual(
dataset.allowed_granularities,
(
OperationalReadGranularity.AGGREGATE,
OperationalReadGranularity.RECORD,
),
)
self.assertIn("numero_pedido", [field.name for field in dataset.allowed_fields])
blocked_fields = {field.name: field.sensitivity for field in dataset.blocked_fields}
self.assertEqual(
blocked_fields["cpf"],
OperationalDataSensitivity.CUSTOMER_IDENTIFIER,
)
self.assertEqual(
blocked_fields["user_id"],
OperationalDataSensitivity.INTERNAL_IDENTIFIER,
)
def test_conversation_turns_dataset_exposes_telemetry_without_raw_messages(self):
dataset = get_operational_dataset("conversation_turns")
self.assertIsNotNone(dataset)
self.assertEqual(
dataset.freshness_target,
OperationalFreshnessTarget.INTRA_HOUR,
)
self.assertIn("tool_name", [field.name for field in dataset.allowed_fields])
blocked_names = {field.name for field in dataset.blocked_fields}
self.assertIn("user_message", blocked_names)
self.assertIn("assistant_response", blocked_names)
self.assertIn("tool_arguments", blocked_names)
self.assertIn("error_detail", blocked_names)
def test_rental_payment_dataset_blocks_receipt_identifiers(self):
dataset = get_operational_dataset("rental_payments")
self.assertIsNotNone(dataset)
blocked_fields = {field.name: field.sensitivity for field in dataset.blocked_fields}
self.assertEqual(
blocked_fields["identificador_comprovante"],
OperationalDataSensitivity.SECRET,
)
self.assertNotIn("identificador_comprovante", [field.name for field in dataset.allowed_fields])
def test_unknown_operational_dataset_returns_none(self):
self.assertIsNone(get_operational_dataset("customers"))
class SystemFunctionalConfigurationContractTests(unittest.TestCase):
def test_catalog_exposes_expected_configuration_domains(self):
self.assertEqual(len(SYSTEM_FUNCTIONAL_CONFIGURATIONS), 6)
self.assertEqual(
{configuration.domain for configuration in SYSTEM_FUNCTIONAL_CONFIGURATIONS},
{
FunctionalConfigurationDomain.MODEL_CATALOG,
FunctionalConfigurationDomain.ATENDIMENTO_RUNTIME,
FunctionalConfigurationDomain.TOOL_GENERATION_RUNTIME,
FunctionalConfigurationDomain.BOT_POLICY,
FunctionalConfigurationDomain.CHANNEL_OPERATION,
FunctionalConfigurationDomain.CONFIG_PUBLICATION,
},
)
def test_director_governed_configurations_require_manage_settings(self):
for configuration in SYSTEM_FUNCTIONAL_CONFIGURATIONS:
self.assertEqual(configuration.read_permission, AdminPermission.VIEW_SYSTEM)
self.assertFalse(configuration.direct_product_write_allowed)
if configuration.mutability == FunctionalConfigurationMutability.DIRECTOR_GOVERNED:
self.assertEqual(configuration.write_permission, AdminPermission.MANAGE_SETTINGS)
self.assertEqual(
configuration.propagation,
FunctionalConfigurationPropagation.VERSIONED_PUBLICATION,
)
else:
self.assertIsNone(configuration.write_permission)
self.assertEqual(
configuration.propagation,
FunctionalConfigurationPropagation.OBSERVATION_ONLY,
)
def test_atendimento_and_tool_generation_profiles_are_separated(self):
atendimento = get_functional_configuration("atendimento_runtime_profile")
tool_generation = get_functional_configuration("tool_generation_runtime_profile")
self.assertIsNotNone(atendimento)
self.assertIsNotNone(tool_generation)
self.assertEqual(
atendimento.domain,
FunctionalConfigurationDomain.ATENDIMENTO_RUNTIME,
)
self.assertEqual(
tool_generation.domain,
FunctionalConfigurationDomain.TOOL_GENERATION_RUNTIME,
)
self.assertNotEqual(atendimento.config_key, tool_generation.config_key)
self.assertEqual(atendimento.source, FunctionalConfigurationSource.ADMIN_GOVERNED_STATE)
self.assertEqual(tool_generation.source, FunctionalConfigurationSource.ADMIN_GOVERNED_STATE)
def test_read_only_configuration_surfaces_are_catalog_and_effective_runtime_state(self):
catalog = get_functional_configuration("allowed_model_catalog")
published_state = get_functional_configuration("published_runtime_state")
self.assertIsNotNone(catalog)
self.assertIsNotNone(published_state)
self.assertEqual(catalog.mutability, FunctionalConfigurationMutability.READ_ONLY)
self.assertEqual(published_state.mutability, FunctionalConfigurationMutability.READ_ONLY)
self.assertEqual(catalog.source, FunctionalConfigurationSource.PLATFORM_CATALOG)
self.assertEqual(
published_state.source,
FunctionalConfigurationSource.PRODUCT_EFFECTIVE_STATE,
)
self.assertTrue(all(not field.writable for field in catalog.fields))
self.assertTrue(all(not field.writable for field in published_state.fields))
def test_unknown_functional_configuration_returns_none(self):
self.assertIsNone(get_functional_configuration("database_credentials"))
class BotGovernedConfigurationContractTests(unittest.TestCase):
def test_catalog_exposes_expected_bot_governance_areas(self):
self.assertEqual(len(BOT_GOVERNED_SETTINGS), 15)
self.assertEqual(
{setting.area for setting in BOT_GOVERNED_SETTINGS},
{
BotGovernanceArea.MODEL_SELECTION,
BotGovernanceArea.RESPONSE_GENERATION,
BotGovernanceArea.TOOL_USAGE,
BotGovernanceArea.FALLBACK_AND_HANDOFF,
BotGovernanceArea.CHANNEL_OPERATION,
},
)
def test_all_bot_settings_are_director_governed_and_versioned(self):
for setting in BOT_GOVERNED_SETTINGS:
self.assertEqual(setting.read_permission, AdminPermission.VIEW_SYSTEM)
self.assertEqual(setting.write_permission, AdminPermission.MANAGE_SETTINGS)
self.assertEqual(
setting.mutability,
BotGovernanceMutability.DIRECTOR_GOVERNED,
)
self.assertTrue(setting.versioned_publication_required)
self.assertFalse(setting.direct_product_write_allowed)
def test_bot_governance_excludes_tool_generation_runtime(self):
self.assertEqual(
{setting.parent_config_key for setting in BOT_GOVERNED_SETTINGS},
{
"atendimento_runtime_profile",
"bot_behavior_policy",
"channel_operation_policy",
},
)
self.assertNotIn(
"tool_generation_runtime_profile",
{setting.parent_config_key for setting in BOT_GOVERNED_SETTINGS},
)
def test_governed_settings_cover_model_policy_and_channel_controls(self):
governed_keys = {setting.setting_key for setting in BOT_GOVERNED_SETTINGS}
self.assertIn("bot_model_provider", governed_keys)
self.assertIn("bot_prompt_profile_ref", governed_keys)
self.assertIn("bot_tool_policy_ref", governed_keys)
self.assertIn("bot_handoff_enabled", governed_keys)
self.assertIn("channel_maintenance_mode", governed_keys)
def test_unknown_bot_governed_setting_returns_none(self):
self.assertIsNone(get_bot_governed_setting("provider_api_key"))
class ModelRuntimeSeparationContractTests(unittest.TestCase):
def test_runtime_profiles_exist_for_attendimento_and_tool_generation(self):
self.assertEqual(len(MODEL_RUNTIME_PROFILES), 2)
self.assertEqual(
{runtime_contract.runtime_target for runtime_contract in MODEL_RUNTIME_PROFILES},
{
ModelRuntimeTarget.ATENDIMENTO,
ModelRuntimeTarget.TOOL_GENERATION,
},
)
def test_attendimento_and_generation_have_distinct_consumers_and_purposes(self):
atendimento = get_model_runtime_contract("atendimento")
tool_generation = get_model_runtime_contract("tool_generation")
self.assertIsNotNone(atendimento)
self.assertIsNotNone(tool_generation)
self.assertEqual(atendimento.config_key, "atendimento_runtime_profile")
self.assertEqual(tool_generation.config_key, "tool_generation_runtime_profile")
self.assertEqual(atendimento.consumed_by_service, ServiceName.PRODUCT)
self.assertEqual(tool_generation.consumed_by_service, ServiceName.ADMIN)
self.assertEqual(atendimento.purpose, ModelRuntimePurpose.CUSTOMER_RESPONSE)
self.assertEqual(
tool_generation.purpose,
ModelRuntimePurpose.TOOL_GENERATION_AND_VALIDATION,
)
self.assertTrue(atendimento.affects_customer_response)
self.assertFalse(tool_generation.affects_customer_response)
self.assertFalse(atendimento.can_generate_code)
self.assertTrue(tool_generation.can_generate_code)
def test_runtime_profiles_require_independent_publication_and_rollback(self):
for runtime_contract in MODEL_RUNTIME_PROFILES:
self.assertEqual(runtime_contract.read_permission, AdminPermission.VIEW_SYSTEM)
self.assertEqual(runtime_contract.write_permission, AdminPermission.MANAGE_SETTINGS)
self.assertTrue(runtime_contract.published_independently)
self.assertTrue(runtime_contract.rollback_independently)
self.assertFalse(runtime_contract.cross_target_propagation_allowed)
self.assertEqual(runtime_contract.catalog_runtime_target, runtime_contract.runtime_target)
def test_separation_rules_forbid_implicit_cross_runtime_propagation(self):
self.assertEqual(
set(MODEL_RUNTIME_SEPARATION_RULES),
{
ModelRuntimeSeparationRule.SEPARATE_CONFIG_KEYS,
ModelRuntimeSeparationRule.SEPARATE_CATALOG_TARGETS,
ModelRuntimeSeparationRule.INDEPENDENT_PUBLICATION,
ModelRuntimeSeparationRule.INDEPENDENT_ROLLBACK,
ModelRuntimeSeparationRule.NO_IMPLICIT_PROPAGATION,
},
)
def test_unknown_model_runtime_returns_none(self):
self.assertIsNone(get_model_runtime_contract("shared_runtime"))
if __name__ == "__main__":
unittest.main()

Loading…
Cancel
Save