ajuste no contas

This commit is contained in:
T3782834
2026-09-01 11:24:03 -03:00
parent 9ed4782f9d
commit 397b831fd3
428 changed files with 2294 additions and 4648 deletions

View File

@@ -1,71 +0,0 @@
# Guardrails e Judges externos — Contas
## Objetivo
O `agent_framework_oci` mantém apenas mecanismos e políticas realmente genéricos. O agente Contas mantém políticas, exemplos e prompts que conhecem TIM, Contas, VAS, fatura, cancelamento ou nomenclaturas comerciais.
## Regra arquitetural
- **Framework:** engine, contratos, execução paralela, fail-fast, telemetria, carregamento YAML e implementações genéricas.
- **Agente:** prompts/policies de domínio e classes externas.
- Um componente externo só é importado quando o YAML do agente declara `type: external`.
- Guardrails/judges nativos continuam funcionando sem qualquer alteração de configuração.
## Configuração de guardrail externo
```yaml
output:
- code: TIM_AOFERTA
type: external
class: app.extensions.tim_guardrails:TimProactiveOfferRail
enabled: true
```
O código `TIM_AOFERTA` deixa explícito que esta política é do agente Contas. O framework continua podendo oferecer `AOFERTA` como rail genérico para outros agentes.
## Configuração de judge externo
```yaml
judges:
- name: tim_groundedness
type: external
class: app.extensions.tim_judges:TimGroundednessJudge
enabled: true
threshold: 0.60
```
## Concorrência e threads
O `ParallelRailExecutor` executa todos os rails concorrentemente. `evaluate()` assíncrono roda no event loop; plugin síncrono roda por `asyncio.to_thread`, portanto não bloqueia o loop. Judges nativos e externos são disparados com `asyncio.gather`; judges síncronos também são deslocados para `asyncio.to_thread`. A ordem da lista de resultados permanece a ordem do YAML.
## Compatibilidade
A extensão é aditiva. Entradas antigas como `{code: PINJ}` e `{name: groundedness}` seguem nativas. Somente itens com `type: external` usam import dinâmico. Isso evita dependência reversa do framework para `app.*`.
## Mapeamento nesta versão do Contas
| Genérico no framework | Específico no Contas | Motivo |
|---|---|---|
| OOS | TIM_OOS | escopo do Contas/TIM |
| AOFERTA | TIM_AOFERTA | política de oferta do atendimento TIM |
| REVPREC | TIM_REVPREC | exemplos e ações transacionais TIM |
| FRASEOLOGIA | TIM_FRASEOLOGIA | fraseologia própria (mantido desabilitado como antes) |
| response_quality | tim_response_quality | prompt original do auditor Contas |
| groundedness | tim_groundedness | prompt original de alucinação/grounding do Contas |
Os prompts originais foram preservados em `app/extensions/tim_prompts/`. As versões sob `agent_framework/.../calibrated/prompts` foram generalizadas e não devem conter nomes comerciais TIM.
## Como criar um novo componente
1. Implemente uma classe no agente com `evaluate(...)`.
2. Para guardrail, retorne `RailDecision`/`RailResult`; para judge, retorne `JudgeResult`.
3. Declare `type: external` e o caminho `module:Class` no YAML.
4. Não crie cliente LLM próprio: use o `llm` fornecido pelo framework/contexto para manter `llm_profiles.yaml`, Langfuse e contabilização.
5. Teste convivência com os rails/judges nativos e o comportamento fail-closed.
## Hardcodes de integração do Contas
Os valores legados de `clientId`, `channel`, `cspId`, sender e URLs que antes apareciam como fallback em Python foram movidos para `config/tim_integration_defaults.yaml`. A precedência é:
1. variável de ambiente;
2. `config/tim_integration_defaults.yaml`;
3. default explícito somente quando a chamada realmente define um default técnico.
Isso preserva contratos diferentes por operação (`TIM_CANCELAMENTO_CHANNEL`, `TIM_DIVERGENCIA_CHANNEL`, etc.) sem usar um `TIM_DEFAULT_*` que altere silenciosamente o legado.
## Validação de contestação
`validate_contestation_items` é regra do domínio Contas e agora vive em `app/domain/contas/contestation_validation.py`. O módulo antigo no framework existe apenas como shim de compatibilidade/depreciação; código novo do Contas importa a implementação do agente diretamente.
## Observabilidade dos códigos externos
O código semântico de um guardrail/judge externo não deve ser alterado para atender um código numérico de um cliente. Use `config/observability_mapping.yaml` para o contrato de telemetria. Exemplo: a extensão pode continuar emitindo `GRL.TOXOUT`, enquanto o contrato publica `GRL.004`. Isso mantém a política do agente separada do catálogo externo de observabilidade.

View File

@@ -1,27 +0,0 @@
# Ajuste semântico do guardrail COER
## Objetivo
Evitar que o COER confunda negação, reclamação, mudança de intenção ou falta de parâmetros com fala incompreensível.
## Alteração
O prompt de `agent_framework.guardrails.calibrated.prompts.coerencia` foi simplificado para uma responsabilidade única: decidir se existe significado conversacional recuperável.
Foram removidas heurísticas textuais específicas de negação, listas de ações e exemplos de frases usados como regras de decisão. O julgamento continua sendo feito pelo LLM com o perfil `guardrail`.
O COER agora não decide intenção, mudança de intenção, continuidade de transação, completude de parâmetros, validade de parâmetros, escopo ou executabilidade. Essas responsabilidades permanecem no router, runtime transacional, validators e clarification.
## Contrato
- compreensível, mas incompleto/ambíguo para negócio: ALLOW;
- compreensível com possível mudança de intenção: ALLOW;
- compreensível faltando parâmetros: ALLOW;
- compreensível com negação/discordância: ALLOW;
- sem significado semântico recuperável: BLOCK.
## Testes
- regressão dirigida do COER: 22 PASS;
- `tests/migration`: 824 PASS / 2 FAIL;
- os 2 FAIL são preexistentes e não relacionados ao COER.

View File

@@ -1,27 +0,0 @@
# Correções após regressão de 31/08/2026
Esta rodada corrige quatro comportamentos observados no relatório de regressão sem reintroduzir o runtime conversacional legado.
## 1. Cancelamento múltiplo após confirmação
O snapshot transacional do framework já preservava corretamente os argumentos confirmados. O defeito estava no preflight de execução do MCP do Contas, que tentava resolver novamente o `subject` de apresentação (por exemplo, `Tamboro Mensal, Paramount+`) mesmo quando `items[]` já continha múltiplas entidades canônicas pré-validadas. Agora `items[]` é a fonte de verdade nessa condição e não há segunda resolução textual.
## 2. Contestação com valor incompatível
Uma divergência de valor comprovada pelo CVAL passa a ser recuperável: o item permanece preservado, apenas `valor` volta para coleta e a resposta informa o valor autoritativo encontrado na fatura. O contrato de auditoria mantém `reason=CVAL` e acrescenta `recoverable_reason=amount_not_supported_by_invoice`.
O runtime genérico ganhou suporte opcional a `parameter_message` emitido por um pre-validator de domínio. O framework apenas apresenta essa mensagem enquanto permanece em `COLLECTING_PARAMETERS`; ele não interpreta a regra de negócio.
## 3. Encerramento explícito
Expressões inequívocas como `entendi, obrigado, era só isso` encerram a sessão como `resolvido`, desde que não exista transação ou workflow ativo. Isso evita que uma despedida caia em fallback/guardrail sem consumir confirmações pendentes.
## 4. Continuação plural após explicação de fatura
Frases como `as duas mesmo, pode seguir` imediatamente após `contas_invoice_explanation` permanecem no contexto de explicação e não são confundidas com finalização genérica.
## Regressão
- Testes novos e direcionados: 58/58 no Contas e 30/30 no runtime transacional do framework.
- `tests/migration`: 818 PASS / 2 FAIL.
- Os 2 FAIL restantes são preexistentes nesta base: caso histórico de `validar_contestacao` com TIM Fashion/R$50 e fraseologia de `termino_desconto`.

View File

@@ -1,63 +0,0 @@
# Correção de paridade conversacional residual do Contas — 2026-08-31
## Escopo
Correções pontuais extraídas do comportamento útil do Contas anterior sem restaurar o runtime legado:
- retenção antes de handoff humano;
- jurídico/Anatel/Procon com dependência de entidade previamente em foco;
- três falas consecutivas realmente incompreensíveis;
- preservação do pós-finalização no framework, com status terminal de erro respeitado;
- cancelamento múltiplo por entidades nomeadas/contextuais e por "todos os VAS avulsos".
## Arquitetura
Foi adicionada `app/domain/contas/conversation_policy.py`, executada depois do `EnterpriseRouter` e antes do agente de domínio. A policy não executa side effects: apenas reprompta, enriquece contexto ou altera o roteamento. Toda operação transacional continua passando pelo `AgentRuntimeMixin`, pré-validação MCP, confirmação explícita e workflow do Contas.
## Retenção
Quando o router solicita handoff humano, a policy procura evidência autoritativa de VAS avulso que participou da variação da conta usando `varied_avulso_items`. Sem evidência, o handoff segue normalmente. Com evidência, a policy oferece dois degraus: explicação da variação e, em seguida, tratamento dos VAS identificados. Recusa do segundo degrau leva ao handoff humano normal.
O aceite do tratamento cria reentrada contextual com os nomes já comprovados; o cliente não precisa repeti-los e a transação continua sujeita à pré-validação e confirmação.
## Jurídico / Anatel / Procon
A mera ameaça regulatória não cria uma transação. Sem entidade concreta em foco, a fala é encaminhada como reclamação ampla ao suporte, sem MCP transacional. Quando já existe `subject/items` em estado transacional, o foco é preservado e a fala pode continuar no fluxo correspondente. A fatura inteira nunca é usada para inventar o alvo.
## Três falas incompreensíveis
Foi criada a intent semântica `contas_no_match`, exclusiva para fala sem conteúdo recuperável. `fallback` genérico não conta como incompreensão. O contador é consecutivo e reinicia em qualquer turno compreendido.
- 1ª: pede reformulação;
- 2ª: pede reformulação;
- 3ª: encerra pelo nó global `end_session`, chamando `finalizar_atendimento` com `status=erro_no_match`.
O limite pode ser configurado por `CONTAS_NO_MATCH_MAX_CONSECUTIVE`, default 3.
## Cancelamento múltiplo
`validar_vas_subject` agora resolve múltiplas entidades exclusivamente contra catálogo autorizado VAS/fatura. Exemplos suportados após extração semântica contextual:
- `cancela Netflix e HBO`;
- `os dois` / `ambos`, quando o extrator LLM consegue resolver os nomes pelo contexto imediato;
- `todos os VAS avulsos`.
`todos` genérico não expande em massa. Para "todos os VAS avulsos", itens estratégicos/bundle são filtrados pela política de domínio. Os itens resolvidos são enviados como `items[]` para o workflow batch existente, com uma única confirmação explícita antes da execução.
## Pós-finalização
Não foi criado runtime duplicado. O lifecycle continua no framework. O nó `end_session` passou apenas a respeitar um `terminal_status` já definido pela policy (por exemplo `erro_no_match`) e uma mensagem terminal específica, mantendo o mecanismo atual de replay/soft reset.
## Testes
Novos testes:
- `tests/migration/test_contas_conversation_policy_residuals.py`;
- `tests/migration/test_multiple_vas_subject_resolution.py`.
Regressão direcionada: **61 PASS**.
Regressão completa `tests/migration`: **810 PASS / 2 FAIL**. Os mesmos dois FAIL foram reproduzidos no ZIP original sem estas alterações, portanto são falhas preexistentes e fora do escopo desta correção:
1. `test_validar_contestacao_aprova_quando_item_e_valor_sao_comprovados`;
2. `test_termino_desconto_e_valor_divergente_preservam_semantica_do_original`.

View File

@@ -1,24 +0,0 @@
# Correção CVAL: valores monetários e itens homônimos
## Problema
O CVAL removia todo ponto de valores textuais antes da conversão decimal. Assim, valores vindos de JSON/backend como `19.99` eram interpretados como `1999`, permitindo indevidamente ajustes como `29.98`.
Além disso, quando o mesmo `subject` aparecia mais de uma vez na fatura, a validação escolhia a primeira ocorrência após a ordenação estrutural, sem usar o valor solicitado para desambiguar a cobrança correta.
## Correção
1. `_parse_amount()` agora reconhece formatos decimais e de agrupamento comuns, incluindo `19.99`, `R$ 19,99`, `1.999,99` e `1,999.99`.
2. Quando existem múltiplos candidatos com o mesmo nome, o CVAL usa o valor solicitado como evidência:
- prefere correspondência exata;
- para ajuste parcial, escolhe a menor ocorrência que comporte o valor solicitado;
- se nenhuma ocorrência comportar o valor, usa a maior ocorrência para que a regra genérica `validated > item_amount` bloqueie a solicitação.
Nenhuma regra específica para "dobro", "triplo" ou percentual foi adicionada.
## Regressões cobertas
- `19.99` permanece `19.99`;
- `R$ 19,99` vira `19.99`;
- `Tamboro Mensal` em `14.99` e `19.99` + solicitação `19.99` resolve a ocorrência correta;
- `Tamboro Mensal` em `14.99` e `19.99` + solicitação `29.98` é bloqueada com `valor_ajuste_maior_que_item`.

View File

@@ -1,29 +0,0 @@
# Correção do cenário 23 — saldo de internet em tempo real
## Problema
O FaturasAgent respondia que não havia informação nos "dados consultados" sobre o saldo de internet restante mesmo quando nenhuma tool de consumo em tempo real havia sido executada. Isso criava uma alegação de consulta sem evidência funcional.
## Solução
A correção foi mantida no agente, sem alteração do framework. Para perguntas explícitas de saldo/franquia restante em tempo real, o FaturasAgent:
1. verifica se alguma tool bem-sucedida trouxe evidência de saldo/consumo atual;
2. se houver evidência, deixa o fluxo normal responder com os dados reais;
3. se não houver evidência, não afirma que consultou dados e orienta o cliente ao Meu TIM.
Resposta de fallback:
> Não consigo consultar o saldo de internet em tempo real por aqui. Para ver quanto ainda resta neste mês, consulte o app Meu TIM, onde você acompanha o consumo atual da sua franquia.
A regra não intercepta perguntas genéricas sobre internet/plano e não bloqueia uma integração futura que passe a devolver saldo real.
## Arquivo alterado
- `app/agents/faturas_agent.py`
## Testes
- `tests/migration/test_scenario_23_live_internet_balance_guidance.py`
- 3/3 testes novos PASS
- regressão `tests/migration`: 821 PASS / 2 FAIL preexistentes

View File

@@ -1,46 +0,0 @@
# Correção: precedência de parâmetros sobre semantic intent shift
## Problema
Durante uma transação ativa em `COLLECTING_PARAMETERS`, o roteador executava o
`semantic_classifier` de mudança de intenção **antes** da extração dos parâmetros
quando `ENABLE_LLM_ROUTER=true`. Com isso, respostas referenciais válidas, como
`"a de quatorze e noventa e nove"`, podiam ser roubadas por outra intent
semanticamente plausível antes de o contrato da transação tentar consumi-las.
## Regra restaurada
A ordem agora é:
1. `AWAITING_CONFIRMATION`: confirmação explícita continua com precedência absoluta.
2. `COLLECTING_PARAMETERS`: tentar primeiro extrair pelo menos um parâmetro pendente.
3. Se algum parâmetro for consumido, manter a transação e **não** executar intent shift.
4. Somente quando nenhum parâmetro for consumido, avaliar `semantic_classifier` para
`CONTINUE`/`SHIFT`.
5. Um novo objetivo explícito continua podendo mudar a intenção, desde que o extrator
corretamente não o converta em parâmetro da transação anterior.
## Resolução contextual
O extrator do roteador agora recebe um contexto conversacional recente e limitado,
apenas como auxílio não-autoritativo para resolver referências. Exemplo: se o histórico
recente contém `Tamboro Mensal = R$ 14,99`, a fala `"a de 14,99"` pode produzir o
candidato `subject=Tamboro Mensal`. A validação/pre-validation da transação continua
sendo responsável por provar a entidade contra evidência de backend/MCP antes da
confirmação ou execução.
## Arquivo principal alterado
- `agent_framework_oci/libs/agent_framework/src/agent_framework/routing/enterprise_router.py`
## Testes
Foram atualizados/adicionados testes em:
- `agent_framework_oci/tests/test_transaction_parameter_llm_precedence.py`
Validação executada:
- 8/8 testes do arquivo de precedência passaram.
- 81/81 testes combinados de transaction routing, state interruption, contextual reentry,
expected input semantic classifier e route stickiness passaram.

View File

@@ -1,51 +0,0 @@
# Correção: valor já coletado pode ser corrigido durante COLLECTING_PARAMETERS
## Problema
Uma transação podia estar em `COLLECTING_PARAMETERS` com um campo obrigatório já preenchido em turno anterior (por exemplo `valor=19.99`) e outro ainda pendente (`subject`). Se o cliente corrigisse o valor no mesmo turno em que identificava o item — por exemplo `desculpa, é a de quatorze e noventa e nove` — o runtime enviava ao extrator LLM apenas os parâmetros ainda ausentes. Assim, `valor` ficava fora do contrato editável do turno e permanecia congelado em `19.99`.
Isso gerava estados inconsistentes como `resolved_value=14.99` e `valor=19.99`, fazendo a contestação executar com o valor antigo.
## Regra corrigida
Enquanto a transação estiver em `COLLECTING_PARAMETERS`, o extrator transacional recebe o conjunto completo de `policy.requires` como campos editáveis do turno. A LLM continua autorizada a devolver somente valores realmente presentes/inequívocos na fala atual. O merge mantém os valores antigos para campos não citados e sobrescreve apenas as chaves efetivamente extraídas.
Precedência resultante:
1. fala atual explicitamente corrige/preenche required field;
2. valor previamente coletado é preservado apenas se a fala atual não o alterar;
3. parâmetros ainda ausentes continuam sendo coletados;
4. somente depois disso é avaliada mudança de intenção.
## Caso de regressão coberto
Estado anterior:
- `valor=19.99`
- `subject` pendente
Mensagem atual:
- `desculpa, é a de quatorze e noventa e nove`
Router/contexto resolve:
- `subject=Tamboro Mensal`
Extrator do runtime corrige:
- `valor=14.99`
Resultado esperado antes da confirmação:
- `subject=Tamboro Mensal`
- `valor=14.99`
## Testes
Foram executados:
- 46 testes de runtime/roteamento/parâmetros transacionais;
- 27 testes de migração ligados a contestação/CVAL/paridade.
Todos passaram.

View File

@@ -1,104 +0,0 @@
# Paridade de Integração Contas — Mock x Sistemas Reais
Data: 2026-08-22
## Objetivo
Validar o agente Contas migrado contra o código original, usando o legado como fonte de verdade para contratos HTTP, autenticação, headers, payloads, URLs, timeouts e comportamento de integração. O objetivo é manter o funcionamento com mocks locais sem impedir a execução contra sistemas reais quando `TIM_GATEWAY_MODE`/`TIM_USE_MOCK_GATEWAY` forem configurados para modo real.
## Correção crítica — identidade do item transacional
Foi corrigido o caso em que um pedido explícito para `TIM CTRL Redes Sociais 8.0` podia ser reinterpretado pelo matcher fuzzy como `TIM Fashion Mensal`.
### Causa
O `InvoiceResolver` eliminava seções não transacionáveis (por exemplo, planos) antes da resolução de identidade e, em seguida, aplicava similaridade somente sobre VAS. Como `TIM Fashion Mensal` ultrapassava o threshold do matcher, o `subject` era substituído antes da execução.
### Correção
- A resolução exata de identidade agora acontece antes de qualquer fuzzy matching.
- A busca exata considera também itens fora do escopo transacional, como planos.
- Um plano encontrado exatamente é classificado como `out_of_scope` para a operação VAS.
- O fuzzy matching continua restrito aos candidatos realmente tratáveis.
- `resolve_items()` preserva o comportamento anterior onde necessário para compatibilidade; o fluxo operacional usa a proteção de identidade.
Resultado esperado para o caso:
`TIM CTRL Redes Sociais 8.0` -> exact match -> `plano` -> `out_of_scope` -> não substituir por outro VAS -> não executar cancelamento.
## Comparação com o código original
Foram comparados os comandos do projeto original (`agente_contas_tim/commands`), `factory.py`, `config.py` e o gateway HTTP com o adaptador atual `app/domain/contas/client.py`.
| Serviço/Integração | Contrato encontrado no original | Situação no migrado após revisão |
|---|---|---|
| Consulta VAS | GET, URL com `{msisdn}` ou append `/msisdn`, normalização para prefixo 55, clientId | Corrigido: aliases originais, timeout, append e prefixo 55 |
| Histórico VAS | GET com `?msisdn=`, clientId/messageId/auth | Corrigido default `clientId=AIAGENTCR` |
| Bloqueio VAS | POST, contratos de payload `pmid`/`input`/`vasBlock`, headers extras | Corrigidos aliases de URL/auth/timeout/clientId/operation/payload/encoding |
| Cancelamento VAS | DELETE, body com channel/msisdn/appId/cspId/interactionProtocol, OAM/CN/type opcionais | Corrigidos aliases `TIM_CANCELLATION_*` e `TIM_CANCELAMENTO_*` |
| Divergência / explicação de fatura | GET `<base>/<msisdn>?channel=AIAGENTCR`, Basic opcional user/password, clientID | Corrigidos aliases, Basic auth e timeout |
| CompleteInvoices | POST `{"msisdn": ...}`, `ClientID=AIAGENTCR` | Compatível; timeout respeitado |
| Profile bill | Factory original usa configuração de CompleteInvoices | Corrigido para priorizar contrato/config de CompleteInvoices |
| Profile full | GET com placeholder ou append `/msisdn`, `ClientID=AIAGENTCR` | Corrigido append e timeout |
| Line info | Mesmo padrão de URL do profile full | Corrigido append e timeout |
| Contrato | GET `<base>/<msisdn>`, clientId do legado | Corrigido default `AIAGENTCR` |
| Protocolo V2 | POST serviceRequest/interaction, headers opcionais OAM/CN/type | Mantido no adaptador atual |
| Contestação do cliente | POST, clientId/messageId/X-Agent-Id, user configurável | Corrigido default via `TIM_CUSTOMER_CONTESTATION_USER_ID` |
| Atualização de Service Request | POST, channel/serviceRequest, headers de integração | Mantido |
| Tracking Activities | POST com customer/protocol/invoice/activity/user | Mantido |
| SMS | POST com msisdn/sender/message/URL e receipt opcional | Mantido |
| Bill PDF detalhada | POST com invoiceId/customerId e invoiceType `DETALHADA`, retorno PDF | Aliases ampliados |
| Secure PDF / invoice recover | GET com invoiceId/msisdn/customerId | Aliases/header ajustados |
## Configurações presentes no legado sem uso operacional comprovado
- `status_customer`: configuração encontrada, mas sem comando/runtime consumidor localizado na revisão.
- configuração OAuth específica de SMS: declarada no config original, mas sem consumidor runtime localizado.
Esses itens não foram tratados como requisito ativo sem evidência de uso no código original.
## Mock x modo real
O mock continua suportado. Em mock, o cliente retorna fixtures locais para as operações previstas. Em modo real, o mesmo adaptador segue os contratos HTTP reconstruídos a partir do código original.
A principal diferença de risco é que um mock tende a responder `200/OK` para cenários preparados. Por isso, a validação de identidade deve ocorrer antes do gateway — como agora ocorre — para impedir que um erro de resolução de entidade seja mascarado pelo mock e, principalmente, que chegue a um backend real.
## Testes executados
### Regressão + contratos existentes
- 101 testes passaram no conjunto de contratos, paridade, idempotência e resolução.
### Novos testes de compatibilidade legado/real
- 7 testes passaram cobrindo:
- prefixo 55 e composição da URL de consulta VAS;
- aliases originais e timeout;
- aliases de cancelamento;
- append de MSISDN em profile full;
- Basic auth de divergência via usuário/senha;
- client IDs de histórico VAS e contrato;
- uso de CompleteInvoices no profile bill.
### Suite `tests/migration`
Resultado observado após as mudanças:
- 660 passed
- 4 failed
As quatro falhas remanescentes são de configuração/contexto de guardrails (`conversation_history` e FRASEOLOGIA) e não estão relacionadas ao `InvoiceResolver` nem aos contratos de integração revisados.
## Limite desta validação
A revisão comprova paridade de contrato em nível de código-fonte e testes locais. Ela não é uma certificação de conectividade real porque não foram usados endpoints, credenciais ou rede dos sistemas legados neste ambiente.
Para homologação real, recomenda-se executar testes de contrato contra um ambiente não produtivo dos serviços TIM, verificando status HTTP, schemas reais, autenticação, timeouts, headers obrigatórios e respostas de erro.
## Gaps de endurecimento recomendados
1. Adicionar validação de readiness no startup quando `mock=false`, falhando cedo se endpoint/auth obrigatórios estiverem ausentes.
2. Criar testes de contrato contra ambiente de homologação para cada integração ativa.
3. Comparar periodicamente fixtures mock com schemas/respostas reais para evitar drift.
4. Manter invariantes transacionais: item solicitado, item resolvido e item executado nunca podem divergir silenciosamente.
5. Evoluir mascaramento/observabilidade do cliente migrado para o mesmo nível do `HttpGateway` original, sem registrar secrets.

View File

@@ -1,472 +0,0 @@
# Manual do Agent Contas Migrado para agent_framework_oci
## 1. Objetivo
Esta versão reconstrói o Agent Contas sobre o `agent_framework_oci` com uma regra arquitetural simples: **o código executável novo não depende do pacote anterior do Contas**. O projeto anterior é apenas referência funcional para preservar regras, contratos de API, fixtures e comportamentos de negócio durante a migração.
O agente novo reutiliza do framework tudo que é infraestrutura genérica: LangGraph, router, stickiness, supervisor, confirmação transacional, clarificação, memória, summary memory, long-term memory, checkpoints, persistence, RAG, embeddings, MCP Tool Router, guardrails, output supervisor, judges, identity, channels, SSE, usage accounting e telemetria.
O novo domínio Contas mantém somente o que é realmente específico da TIM: chamadas de faturas, VAS, contestação, protocolos, tracking, SMS, Secure PDF e regras que relacionam essas operações.
## 2. Regra de independência
A Definition of Done da migração é:
```bash
grep -R "agente_contas_tim" app mcp config
```
Resultado esperado: nenhuma ocorrência/import do pacote anterior.
O pacote entregue já inclui `tests/migration/test_no_legacy_dependency.py` para impedir regressão dessa regra.
## 3. Arquitetura
```text
Canal / Frontend
|
v
app/main.py
|
v
agent_framework_oci
|-- ChannelGateway / IdentityResolver
|-- LangGraph / AgentWorkflow
|-- EnterpriseRouter / Route Stickiness / Supervisor
|-- Guardrails / Output Supervisor / Judges
|-- Memory / Summary Memory / LTM / Checkpoints
|-- RAG / Embeddings / Cache
|-- MCPToolRouter
|-- Langfuse / Analytics / OTEL / OCI Streaming
|
v
MCP Contas :8400
|
v
app/domain/contas
|-- TimApiClient
|-- ContasDomainService
`-- fixtures de desenvolvimento
|
v
APIs TIM
```
Não existe um segundo LangGraph, LLM gateway, confirmation manager, workflow engine ou memory store dentro do MCP.
## 4. Agentes de domínio
A versão migrada possui quatro agentes reais do domínio Contas:
| Agente | Responsabilidade |
|---|---|
| `faturas_agent` | Consulta de faturas, composição, variação e explicação de cobrança |
| `vas_agent` | Consulta de VAS, histórico, serviços estratégicos/bundles e informação de serviços |
| `contestacao_agent` | Cancelamento transacional de VAS e contestação de cobrança |
| `suporte_contas_agent` | Protocolos, acompanhamento, suporte e encerramento |
Todos herdam `AgentRuntimeMixin` do framework. Eles não implementam máquina de confirmação/clarificação própria.
## 5. LangGraph do framework
O fluxo principal é o `StateGraph` do `agent_framework_oci` usado em `app/workflows/agent_graph.py`:
```text
START
-> input_guardrails
-> load_long_term_memory
-> routing_decision
-> agente de domínio
-> output_supervisor
-> output_guardrails
-> judge
-> supervisor_review
-> persist_long_term_memory
-> persist
-> END
```
O `EnterpriseRouter` decide a intent, agente e tools. O route stickiness decide continuidade da conversa. O runtime do framework controla coleta de parâmetros e confirmação de tools transacionais.
## 6. Transações
As tools abaixo são transacionais em `config/tool_policies.yaml`:
- `cancelar_vas_avulso`
- `tratar_vas_estrategico`
- `contestar_cobranca`
A confirmação ocorre **antes** da chamada MCP e é responsabilidade do `AgentRuntimeMixin`. O domínio recebe a chamada somente depois de a política do framework permitir execução.
Isso evita o problema clássico de um "sim" responder à pergunta errada: a confirmação está vinculada ao estado transacional/tool pendente do framework, não a heurísticas no prompt.
## 7. RAG
Conhecimento conceitual não é uma API TIM e por isso não é implementado como "workflow de busca" dentro do MCP.
O projeto usa diretamente:
- `RagService`
- `create_embedding_provider()`
- `VECTOR_STORE_PROVIDER`
- `GRAPH_STORE_PROVIDER`
- `EMBEDDING_PROVIDER`
`buscar_informacao` permanece desabilitada no catálogo MCP; perguntas de conhecimento passam pelo RAG nativo do framework.
## 8. Funcionalidades migradas
| Funcionalidade do Contas | Nova implementação | Responsabilidade do framework |
|---|---|---|
| Consulta de faturas | `ContasDomainService.consultar_faturas` | seleção da tool, identity, cache, resposta |
| Explicação de fatura | API de fatura + billing analysis como evidência | LLM produz explicação grounded |
| Consulta VAS | `consultar_vas` | routing/tool selection |
| Histórico VAS | `consultar_historico_vas` | routing/tool selection |
| Cancelamento VAS avulso | consulta -> match -> bloqueio -> cancelamento | parâmetros + confirmação + estado |
| VAS estratégico/bundle | domínio retorna serviço e orientação | conversa/continuidade no LangGraph |
| Contestação | faturas/contrato/profile -> protocolo -> contestação -> tracking | parâmetros + confirmação + estado |
| Status de solicitação | `consultar_status_solicitacao` | roteamento e contexto |
| SMS | `enviar_sms` | tool policy/contexto |
| Secure PDF | `recuperar_fatura_pdf` | roteamento/identity |
| Encerramento | efeitos de domínio opcionais | `end_session`, memória e telemetria |
| Guardrails | nenhum código local duplicado | framework |
| Judges | nenhum código local duplicado | framework |
| Memória | nenhum store local de conversa | framework |
| LTM | nenhum mecanismo local | framework |
| Checkpoint | nenhum `MemorySaver` dentro do MCP | framework |
| Telemetria | eventos do runtime/framework | framework |
## 9. Estrutura do projeto
```text
app/
main.py
state.py
agents/
faturas_agent.py
vas_agent.py
contestacao_agent.py
suporte_contas_agent.py
domain/contas/
client.py
service.py
fixtures/
workflows/
agent_graph.py
observability/
config/
routing.yaml
tools.yaml
tool_policies.yaml
mcp_servers.yaml
mcp_parameter_mapping.yaml
identity.yaml
prompts/
contas_mcp/servers/contas_mcp_server/
main.py
agent_framework_oci/
... framework reutilizado ...
```
## 10. Arquivo `.env`
O `.env` fornecido para esta reconstrução foi preservado byte a byte no pacote. Não foi recomposto nem reduzido.
O arquivo contém dois grupos:
1. configurações do `agent_framework_oci`;
2. variáveis TIM de domínio/compatibilidade já compiladas para os ambientes.
Embora algumas variáveis antigas possam deixar de ser usadas depois da migração, elas foram mantidas para não perder o trabalho de consolidação. A remoção deve ocorrer apenas após testes de DEV/FQA/PRD.
### Variáveis principais do framework
- `LLM_PROVIDER`
- `OCI_AUTH_MODE`, `OCI_CONFIG_FILE`, `OCI_PROFILE`, `OCI_COMPARTMENT_ID`, `OCI_REGION`
- `SESSION_REPOSITORY_PROVIDER`
- `MEMORY_REPOSITORY_PROVIDER`
- `CHECKPOINT_REPOSITORY_PROVIDER`
- `VECTOR_STORE_PROVIDER`
- `GRAPH_STORE_PROVIDER`
- `EMBEDDING_PROVIDER`
- `ENABLE_LANGFUSE`
- `ENABLE_INPUT_GUARDRAILS`
- `ENABLE_OUTPUT_GUARDRAILS`
- `ENABLE_JUDGES`
- `ENABLE_SUPERVISOR`
- `ENABLE_ROUTE_STICKINESS`
- `ENABLE_MCP_TOOLS`
- `ENABLE_CONVERSATION_SUMMARY_MEMORY`
- `ENABLE_LONG_TERM_MEMORY`
### Modo mock x APIs TIM reais
Mock atual:
```env
TIM_GATEWAY_MODE=mock
TIM_USE_MOCK_GATEWAY=true
```
Integrações reais:
```env
TIM_GATEWAY_MODE=real
TIM_USE_MOCK_GATEWAY=false
```
Os dois valores devem estar coerentes.
## 11. Integrações TIM
| Integração | Variável | Método | VPN TIM provável |
|---|---|---|---|
| Complete Invoices | `TIM_COMPLETE_INVOICES_URL` | POST | Sim em FQA interno |
| Billing Analysis | `TIM_DIVERGENCIA_URL` | POST | Sim |
| Consulta VAS | `TIM_URL_CONSULTA_VAS` | GET | Sim |
| Histórico VAS | `TIM_VAS_HISTORY_URL` | GET | Sim |
| Bloqueio VAS | `TIM_URL_BLOQUEIO_VAS` | POST | Sim |
| Cancelamento VAS | `TIM_CANCELAMENTO_URL` | DELETE | Sim |
| Contrato | `TIM_CONTRATO_URL` | GET | Sim |
| Full Profile | `TIM_PROFILE_FULL_URL` | GET | Sim |
| Contestação | `TIM_CUSTOMER_CONTESTATION_URL` | POST | Sim |
| Protocolo | `TIM_PROTOCOL_URL` | POST | Sim |
| Service Request Status | `TIM_SERVICE_REQUEST_STATUS_URL` | POST | Sim |
| Tracking Activities | `TIM_TRACKING_ACTIVITIES_URL` | POST | Sim |
| SMS | `TIM_SMS_URL` | POST | Sim |
| Secure PDF | `TIM_URL_INVOICE_RECOVER` | POST | Sim |
Os endpoints FQA do `.env` usam `pmidfqa.internal.timbrasil.com.br`; portanto DNS/rota corporativa precisa estar disponível para teste real.
## 12. Outras integrações
| Integração | Uso | Ativação |
|---|---|---|
| OCI GenAI | LLM | `LLM_PROVIDER=oci_sdk` + `OCI_AUTH_MODE` |
| Autonomous DB | session/memory/checkpoint/vector/usage | providers `autonomous` + `ADB_*` |
| OCI Embeddings | RAG | `EMBEDDING_PROVIDER=oci` |
| Langfuse | tracing | `ENABLE_LANGFUSE=true` |
| GCP Pub/Sub | analytics corporativo | `ENABLE_ANALYTICS=true`, provider Pub/Sub e credencial GCP |
| MongoDB | sequence Pub/Sub | `PUBSUB_SEQUENCE_PROVIDER=mongodb` |
| Redis | cache/sequence opcional | `ENABLE_REDIS_CACHE=true` ou provider sequence redis |
| OTEL | logs/traces | `ENABLE_OTEL=true` + endpoint |
| OCI Streaming | eventos alternativos | `ENABLE_OCI_STREAMING=true` |
## 13. Instalação local
Recomendado: Linux/WSL com Python 3.13.
```bash
cd contas_migrado_framework_native
uv sync
```
Se o `uv` ainda não estiver disponível, instale-o conforme o padrão do seu ambiente e depois execute `uv sync`.
## 14. Subir o MCP Contas
Terminal 1:
```bash
uv run uvicorn contas_mcp.servers.contas_mcp_server.main:app \
--host 0.0.0.0 --port 8400
```
Validar:
```bash
curl http://localhost:8400/health
curl http://localhost:8400/mcp/tools/list
```
`/health` deve reportar:
```json
{
"status": "ok",
"architecture": "framework-native",
"legacy_dependency": false
}
```
## 15. Subir o Agent Contas
Terminal 2:
```bash
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
```
Validar:
```bash
curl http://localhost:8000/health
```
## 16. Smoke test do MCP em mock
```bash
PYTHONPATH=".:agent_framework_oci/libs/agent_framework/src" \
python scripts/smoke_mcp.py
```
Esse teste cobre faturas, invoice explanation, VAS, histórico, cancelamento e contestação usando fixtures migradas para o novo domínio.
## 17. Testar o agente pelo Gateway
Exemplo de consulta:
```bash
curl -X POST http://localhost:8000/gateway/message \
-H 'Content-Type: application/json' \
-d '{
"channel":"web",
"agent_id":"telecom_contas",
"tenant_id":"default",
"payload":{
"text":"Quero consultar minha fatura",
"session_id":"contas-test-001",
"user_id":"user-001",
"msisdn":"11999999999",
"message_id":"msg-001"
}
}'
```
Depois teste continuidade na mesma `session_id`.
## 18. Teste transacional de VAS
1. Envie: `Quero cancelar TIM Fashion Mensal`.
2. O framework deve identificar tool transacional e pedir confirmação.
3. Responda `sim` na mesma sessão.
4. Somente então `cancelar_vas_avulso` deve ser chamada.
5. Em modo mock, o resultado deve conter `block.status=200` e `cancellation.status=200`.
Teste negativo importante:
1. Entre em estado aguardando confirmação de cancelamento.
2. Envie `você ainda está por aí?`.
3. A frase não pode confirmar a transação.
## 19. Teste de contestação
Em mock:
```text
Quero contestar Tamboro Mensal no valor de 14,99, não reconheço essa cobrança.
```
Esperado:
- route `contestacao_agent`;
- parâmetros `subject` e `valor` coletados;
- confirmação antes da mutação;
- abertura de protocolo;
- contestação;
- tracking;
- resposta final grounded nos retornos da tool.
## 20. Testes de memória
### Short-term / summary
Na mesma sessão:
```text
Meu serviço é TIM Fashion Mensal.
...
Qual serviço eu mencionei antes?
```
### Long-term memory
Com `ENABLE_LONG_TERM_MEMORY=true`, grave uma informação elegível, encerre a sessão e abra outra sessão com a mesma identidade de negócio. Verifique se o contexto é recuperado conforme as regras de LTM do framework.
## 21. Testes de RAG
Valide que a intent de conhecimento não dispara MCP desnecessariamente. Exemplos:
```text
O que significa cobrança proporcional?
Como funciona o vencimento da fatura?
```
O trace deve mostrar `RagService`; a tool `buscar_informacao` não precisa ser executada.
## 22. Testes de guardrails e judges
Com as flags habilitadas no `.env`:
- prompt injection deve passar pelos input guardrails;
- resposta candidata passa pelo Output Supervisor/output guardrails;
- groundedness deve considerar `mcp_results` quando a resposta usa dados TIM;
- judges rodam após a geração e antes da persistência final.
Use o Langfuse para observar a sequência de nodes do LangGraph.
## 23. Teste de conectividade/VPN
O pacote contém:
```bash
python scripts/check_integrations.py
```
O script resolve DNS e testa TCP dos endpoints configurados. Execute antes e depois de conectar a VPN.
Para APIs FQA, um resultado `DNS_FAIL`, `TCP_FAIL` ou timeout indica que a rede ainda não está pronta. Um `TCP_OK` prova conectividade de rede, mas não autenticação/contrato HTTP.
## 24. Ativar APIs reais gradualmente
Não habilite todas as mutações de uma vez. Ordem recomendada:
1. VPN/DNS;
2. `consultar_faturas`;
3. `consultar_vas`;
4. `consultar_historico_vas`;
5. contrato/profile;
6. billing analysis;
7. Secure PDF;
8. protocol/status/tracking;
9. SMS;
10. cancelamento VAS;
11. contestação.
Depois faça teste end-to-end completo.
## 25. Kubernetes
Use o mesmo `.env` como fonte para construir ConfigMap/Secret, separando segredos no mecanismo corporativo apropriado. O backend necessita alcançar:
- MCP Contas;
- OCI GenAI;
- Autonomous DB;
- Langfuse, se habilitado;
- endpoints TIM internos em modo real;
- providers de analytics habilitados.
O MCP pode rodar no mesmo pod como sidecar ou, preferencialmente, como deployment/service separado. Configure `config/mcp_servers.yaml` para o DNS do Service Kubernetes.
## 26. Critérios de aceite da migração
A migração é considerada concluída quando:
- [ ] zero imports/referências executáveis ao pacote anterior;
- [ ] backend e MCP sobem após o diretório anterior ser removido;
- [ ] read-only APIs funcionam em FQA;
- [ ] cancelamento exige confirmação do framework e funciona em FQA;
- [ ] contestação exige confirmação e reproduz efeitos esperados;
- [ ] RAG usa `RagService` do framework;
- [ ] memory/summary/LTM/checkpoint usam providers do framework;
- [ ] guardrails e judges aparecem nos traces;
- [ ] LangGraph é a única máquina de estados conversacional;
- [ ] Pub/Sub/sequence/OTEL/Langfuse são validados conforme ambiente;
- [ ] testes de carga são executados antes de produção.
## 27. Segurança do `.env`
O arquivo preservado contém material sensível. Ele foi mantido porque isso foi um requisito explícito da reconstrução. Para distribuição fora do ambiente controlado, rotacione credenciais expostas e substitua valores por Secrets/Vault/Key Vault/Kubernetes Secret conforme política corporativa.

File diff suppressed because it is too large Load Diff

View File

@@ -1,223 +0,0 @@
# Matriz de Migração — Contas -> agent_framework_oci
| Capacidade | Destino novo | Reuso framework | Código de domínio novo | Dependência anterior |
|---|---|---:|---:|---:|
| LangGraph | `app/workflows/agent_graph.py` | Sim | composição mínima | Não |
| Router | EnterpriseRouter | Sim | routing.yaml | Não |
| Stickiness | framework | Sim | configuração | Não |
| Supervisor | framework | Sim | configuração | Não |
| Confirmação | AgentRuntimeMixin | Sim | tool policy | Não |
| Clarificação | AgentRuntimeMixin/MCP mapping | Sim | schemas/mapping | Não |
| Sessions | framework repository | Sim | Não | Não |
| Message memory | framework | Sim | Não | Não |
| Summary memory | framework | Sim | Não | Não |
| LTM | framework | Sim | Não | Não |
| Checkpoint | framework | Sim | Não | Não |
| RAG | RagService | Sim | conteúdo/config | Não |
| Guardrails | GuardrailPipeline | Sim | config | Não |
| Output Supervisor | framework | Sim | Não | Não |
| Judges | JudgePipeline | Sim | config | Não |
| MCP router | framework | Sim | tool catalog | Não |
| Faturas | MCP/domain | Não aplicável | Sim | Não |
| Billing Analysis | MCP/domain | Não aplicável | Sim | Não |
| Consulta/Histórico VAS | MCP/domain | Não aplicável | Sim | Não |
| Bloqueio/Cancelamento VAS | MCP/domain | confirmação no framework | Sim | Não |
| Contestação | MCP/domain | confirmação/estado no framework | Sim | Não |
| Protocol/Status/Tracking | MCP/domain | contexto no framework | Sim | Não |
| SMS | MCP/domain | contexto no framework | Sim | Não |
| Secure PDF | MCP/domain | contexto no framework | Sim | Não |
| Langfuse | framework | Sim | Não | Não |
| Pub/Sub/sequence | framework | Sim | configuração | Não |
| OCI Streaming | framework | Sim | configuração | Não |
| OTEL | framework | Sim | configuração | Não |
## Regra
O pacote anterior não é uma biblioteca do novo projeto. Se uma regra específica for necessária, ela deve ser portada e testada dentro de `app/domain/contas`; infraestrutura genérica deve ser eliminada em favor do framework.
## Contratos TIM validados por regressão
| Integração | Paridade coberta | Estado |
|---|---|---|
| CompleteInvoices | método/payload/header `ClientID` | ✅ |
| Query VAS | URL por MSISDN, `clientId=AIAAGENTCR`, auth | ✅ |
| VAS History | query `msisdn`, `clientId`, `messageId`, auth | ✅ |
| Block VAS | payload PMid + fallbacks e headers | ✅ |
| Cancel VAS | DELETE, channel, protocol, headers, messageId | ✅ |
| Contract Information | GET por MSISDN, `clientId`, auth | ✅ |
| Profile/Line Info | GET e header `ClientID` | ✅ |
| Billing Analysis | GET por MSISDN + channel | ✅ |
| Bill PDF | POST detalhado + criptografia | ✅ |
| Secure PDF | GET com parâmetros criptografados | ✅ |
| Customer Contestation | payload/headers principais | ✅ |
| Service Request Status | envelope `serviceRequest` | ✅ |
| Tracking Activities | customer/invoice/activity/user | ✅ |
| Protocol V2 | envelope Siebel + headers corporativos | ✅ |
| SMS Barcode | payload completo + retry/RCT | ✅ |
## Jornadas compostas e comportamento conversacional
| Capacidade original | Implementação migrada | Reuso do framework | Regressão |
|---|---|---:|---:|
| Cancelamento VAS -> contestação | dois workflows encadeados no MCP | `WorkflowRuntime` | ✅ |
| Composição final cancelamento | `app/domain/contas/vas_cancellation_message.py` | domínio determinístico | ✅ 19 casos originais |
| Fallback VAS History | `ContasDomainService.cancelar_vas_avulso` | transporte via adapter | ✅ |
| Cancelamento parcial em lote | action expõe cancelados/falhas/candidatos | WorkflowRuntime + IdempotencyStore | ✅ |
| Idempotência transacional | `create_idempotency_store()` | framework | ✅ |
| Replay pós-finalização | Channel short-circuit | framework | ✅ |
| Idle nudge replay | Channel short-circuit | framework | ✅ |
| Processing interruption | replay + classificador LLM fail-safe | `LLMProvider` framework | ✅ |
| Correção Fim/Mim -> Sim | channel transcription | framework | ✅ |
| Finalização status/summary | regra pura de domínio | workflow framework | ✅ |
| Protocolo informacional final | action + ProtocolV2 | WorkflowRuntime | ✅ |
### Bootstrap MCP
`WorkflowRuntime`, checkpointer e `IdempotencyStore` são inicializados de forma lazy. Isto evita dependência de Oracle/Redis para endpoints de diagnóstico e garante que o backend durável só seja aberto quando um workflow realmente precisar ser executado.
## Incrementos de paridade - baseline 420
| Capacidade original | Implementação migrada | Responsabilidade | Estado |
|---|---|---|---:|
| InvoiceContextProvider / prefetch | `InvoiceContextService` + `agent_framework.cache.Cache` | domínio escolhe evidências; framework fornece cache | ✅ |
| Isolamento de invoice context por sessão | chave `session_id:msisdn:invoice_id` | framework cache | ✅ |
| Plano família titular/dependente | normalização antes do workflow + contestação única no titular | domínio + WorkflowRuntime | ✅ |
| Correção de linha por invoice detail | normalização determinística | domínio | ✅ |
| CVAL fail-stop | edge `success=false -> END` | WorkflowRuntime | ✅ |
| Snapshot parcial em falha | `WorkflowRuntime` recupera último state do LangGraph | framework | ✅ |
| Retry Billing Analysis / RCT 079-084 | metadata `_transport` + `RCTPolicy` | domínio define códigos; observer publica | ✅ |
| Finalização invoice explanation | protocolo informacional somente após workflow executado | domínio + WorkflowRuntime | ✅ |
| VEB fechado / force RT15 | reuso ou novo protocolo conforme flags | domínio | ✅ |
| Inicialização AgentWorkflow | router/agentes/grafo dentro de `__init__` | aplicação/framework | ✅ |
### Incrementos de paridade — baseline 435
| Capacidade original | Implementação migrada | Responsabilidade | Status |
|---|---|---|---|
| Invoice prefetch single-flight | `InvoiceContextService` + `agent_framework.cache.Cache` | domínio escolhe evidências; framework fornece cache | ✅ |
| CVN de prefetch sem duplicação | `business_events` + cache markers | domínio define código; observer framework publica | ✅ |
| Latch invoice/workflow já executado | `AgentRuntimeMixin.business_workflows_executed` | framework | ✅ |
| Batch cancellation max 5 | action async + semaphore | domínio action sobre WorkflowRuntime | ✅ |
| Protocolo por linha antes de cancelar | action `cancelamento_vas_avulso_batch` | domínio + adapter TIM | ✅ |
| Erro estruturado de workflow | `WorkflowRunResult.error_details` | framework | ✅ |
| Provider error de contestação | MCP mapping sobre `error_details` | domínio/MCP fino | ✅ |
## Baseline 440 testes - continuação
- 440 testes de migração passando; 2 skipped por dependências indisponíveis neste runtime (`langgraph`/`jellyfish`).
- Cancelamento VAS: falha de bloqueio/cancelamento pode permanecer candidata à contestação sem mascarar sucesso.
- Resposta composta preserva protocolos de cancelamento por linha + protocolo de contestação e sinaliza finalização sugerida.
- Plano família: protocolo de cancelamento em dependente resolve `socialSecNo` via LineInfo da própria linha.
- Registro de protocolo aceita aliases de resposta `interactionProtocol`, `protocolNumber`, `protocol` e `protocolo`.
- Finalização informacional recalcula Bundle/Estratégico/Avulso pela evidência de `invoice_detail`/Billing Analysis quando disponível.
## Finalização - paridade adicional (baseline 533)
| Capability original | Implementação migrada | Framework reutilizado | Evidência |
|---|---|---|---|
| CVN aceite/recusa no encerramento | `finalizar_atendimento_action` | `business_events` + `AgentObserver` | testes de finalização estendida |
| Protocolo RT-15 informacional | action de domínio + TIM client | WorkflowRuntime/observer/idempotência | RCT.085/086 + CVN.010/011 |
| Nota de invoice explanation | valor canônico `Explicação dos valores da fatura` | workflow latch do framework | regressão |
| Handoff/retention suppression | flags no state/domain action | estado persistido do framework | regressão |
| SAD decision tree | `SAD.001/002/003/004/005/006/007` como business events | AgentObserver | regressão |
| Classificação VAS sem invoice detail | aliases determinísticos de domínio | nenhuma engine paralela | regressão |
| Regressão offline de workflow | `WorkflowRuntime(... allow_deterministic_fallback=True)` somente em teste | DSL/actions do framework | 18 casos históricos executados |
## Atualização de paridade - baseline 550
| Capability | Original | Migrado | Evidência |
|---|---|---|---|
| Finalização com prefetch de fatura | CVN lookup + RT-15 quando aplicável | Implementado | regressão de finalização |
| Supressão após transição para negócio | não reemite CVN/MPI | Implementado | regressão dedicada |
| VEB terminal | não duplica RT-15/CVN | Implementado | regressão dedicada |
| Precedência de tipo pela fatura | total/parcial/sem match | Implementado | regressão dedicada |
| Protocolo já existente | não duplica RT-15 | Implementado | regressão dedicada |
| Matcher fonético/transcrição | catálogo real | Implementado sem xfails | suíte de transcrição |
## VAA — rastreabilidade de cancelamento/contestação
| Capability histórica | Implementação migrada | Framework reutilizado | Estado |
|---|---|---|---|
| VAA.001004 cancelamento | `cancelamento_vas_avulso_batch` retorna `business_events` | AgentObserver / analytics | OK |
| VAA.005009 contestação/boleto | `abrir_contestacao_cliente` retorna `business_events` | AgentObserver / analytics | OK |
| VAA.012015 SMS | `enviar_sms` retorna `business_events` e mantém fluxo em erro | AgentObserver / analytics | OK |
| VAA.016017 status SR | `atualizar_status_sr` retorna `business_events` | AgentObserver / analytics | OK |
## Baseline 560 testes - metadata corporativa TIM
- 560 testes de migração passando, sem skips/xfails.
- Business events preservam contexto corporativo: `agentProtocolId`, `adjustedProtocol`, `billingId`, `uraCallId`, `channelId`, `sessionId`, `messageId` e `agentSpecificData`.
- Contestação VAA.005-009 preserva itens/valores ajustados e protocolo.
- Finalização CVN.010/011 preserva protocolo TIM/URA e billing context.
- RCTs derivados de retry HTTP carregam `apiUrl`, `apiStatusCode`, `apiResponsePayload` e `latencyMs`.
- SMS e Service Request Status carregam metadata de transporte e contexto de protocolo.
- `tim_payload_mapper` aceita o formato histórico `agentSpecificData` JSON-string e o converte para o objeto canônico TIM no payload final.
### Paridade de eventos conversacionais — baseline 565
| Família | Paridade adicionada |
|---|---|
| VEB | ordem dos branches de VAS estratégico e metadata do turno/URA |
| MPI | contexto de invoice explanation, pró-rata e cancelamento |
| CVN | contexto conversacional/protocolo no encerramento |
| SAD | `llmResponse`, `messageId`, sessão/canal e `sessionEndAt` normalizado |
## Incremento de paridade — baseline 570
| Funcionalidade original | Implementação migrada | Responsabilidade |
|---|---|---|
| Cancelamento solicitado para item estratégico/bundle | `InvoiceResolver` redireciona para workflow `vas_estrategico` | Domínio + WorkflowRuntime |
| Invoice explanation SIM | recomenda `resolvido` pelo último node do workflow | MCP adapter fino sobre WorkflowRuntime |
| Invoice explanation NÃO sem VAS variado | recomenda `nao_resolvido` | MCP adapter fino sobre WorkflowRuntime |
| Pró-rata aceito / sem Plano Controle | recomenda `resolvido` | MCP adapter fino sobre WorkflowRuntime |
| Orientação de cancelamento de VAS estratégico por parceiro | action declara `requires_rag/rag_queries`; `AgentRuntimeMixin` chama `RagService` | Framework |
| Gate padrão de regressão | `pytest -q` -> `tests/` | Projeto migrado |
## Baseline 578 - wrapper cancelamento + LLM composition
- 593 testes passando; zero skipped/xfail.
- Cancelamento preserva `auto_finalize_on_failure` em falha técnica de contestação.
- Item já contestado não mascara cancelamento concluído como falha sistêmica.
- `cancelamento_vas_protocol` e todos os protocolos de resposta são preservados.
- Plano família contesta titular + dependentes em uma única `contestacao_tool`.
- Lote com muitos no-match continua contestando exatamente os candidatos elegíveis.
- Pró-rata usa `requires_llm_composition` do framework em vez de gateway LLM de domínio.
### Paridade de wrappers históricos — baseline 593
| Comportamento original | Implementação migrada | Estado |
|---|---|---|
| `tipo_atendimento=contestacao` | `_workflow_payload(contestacao_tool)` | ✅ |
| Contexto do turno no workflow | payload MCP preserva IDs/canal/mensagem | ✅ |
| CPF como alias de socialSecNo | normalização no wrapper composto | ✅ |
| `cancelados` sem `results.success` | fallback agregado do wrapper | ✅ |
| `itens_para_contestacao` | alias de `contestation_candidates` | ✅ |
| falha block/cancel ainda elegível a RT-02 | composição de dois WorkflowRuntime | ✅ |
| SMS falha sem derrubar jornada | `sms_not_send_error` | ✅ |
| Bundle + Estratégico + NÃO | protocolo deferido + RAG obrigatório | ✅ |
## Complemento de paridade — baseline 599
| Comportamento histórico | Implementação migrada | Prova |
|---|---|---|
| Itens já contestados | normalização em `abrir_contestacao_cliente` + compositor determinístico | teste de wrapper 1:1 |
| Itens contestados/não contestados | classificação na action de domínio | regressão de contestação |
| Total contestado somente dos itens aceitos | cálculo determinístico no domínio | regressão de contestação |
| Contestação retorna valor zero | fallback para total efetivamente cancelado | teste de wrapper 1:1 |
| Valor na fala em pt-BR | normalização na borda MCP | teste `R$ 14,99` |
| `next_subject` | ignorado pelo compositor determinístico | teste de wrapper 1:1 |
| Billing Analysis indisponível | fraseologia canônica e `auto_finalize_on_failure=false` | regressão invoice explanation |
### Cobertura 1:1 de wrappers — baseline 615
Além dos testes de domínio/actions/workflows, a suíte passa a reproduzir diretamente
outcomes históricos dos wrappers `cancelar_vas_single` e `finalize_support`, cobrindo
no-match, candidatos explícitos, falhas parciais RT-01→RT-02, SMS, protocolos,
`protocol_closed`, plano família/titular-dependente e protocolo informacional deferido.
Esses testes são classificados como **paridade explícita de contrato externo**, e não
apenas cobertura indireta por actions internas.

View File

@@ -1,67 +0,0 @@
# Mapeamento contratual da observabilidade do Contas
O Contas usa o mecanismo genérico `ObservabilityCodeMapper` do framework para adaptar **identificadores internos de observabilidade** aos códigos exigidos pelo contrato externo.
Arquivo:
```text
config/observability_mapping.yaml
```
Configuração de exemplo deste agente:
```yaml
version: "1"
mappings:
guardrail.dlex_in: GRL.004
guardrail.tox: GRL.005
```
O mapping é feito pelo nome canônico emitido internamente. Assim, uma generation/observation criada como `guardrail.dlex_in` aparece externamente como `GRL.004`, e `guardrail.tox` como `GRL.005`.
A substituição acontece antes dos providers de observabilidade. O mesmo nome contratual é usado por Langfuse, OTEL e EventBus nos caminhos que passam por `Telemetry`. Eventos publicados pelo `AgentObserver` também continuam usando o mesmo mapper.
Quando um nome de span/generation é substituído, o nome interno é preservado em metadata:
- `observability_name_internal`
- `observability_name_mapped`
- `observability_code_mapped: true`
Para eventos estruturados, permanecem disponíveis os campos equivalentes `event_code_internal` e `event_code_mapped`.
Códigos/names ausentes na tabela passam sem alteração. Para acrescentar outro contrato, adicione somente uma nova entrada ao YAML; não altere Python nem o guardrail/judge.
Este arquivo pertence ao agente/deployment. O framework contém apenas a engine genérica de mapping e não conhece os códigos contratuais deste agente ou de qualquer cliente.
## Diagnóstico de carregamento
No startup o agente registra uma linha `Observability mapping:` com `enabled`, `path`, quantidade de entradas, amostras resolvidas e o arquivo real de onde `agent_framework` foi importado. Isso permite detectar `.venv` antigo/cópia errada do framework e path relativo incorreto.
A normalização é aplicada em duas barreiras:
1. `Telemetry._start_observation()` — última barreira para spans/generations criados pelo Telemetry;
2. `LangfuseAnalyticsPublisher` — necessário porque esse publisher usa o SDK Langfuse diretamente e não passa pelo Telemetry.
Assim, uma configuração como:
```yaml
mappings:
guardrail.dlex_in: GRL.004
guardrail.tox: GRL.005
```
é aplicada independentemente de qual dos dois caminhos produziu a observation.
## Normalização na fronteira do LLM provider
A normalização não depende apenas do `Telemetry`. O `generation_name` é resolvido pelo `ObservabilityCodeMapper` antes de o provider LLM iniciar qualquer instrumentação. Isso garante que nomes como `guardrail.dlex_in` já cheguem ao tracer como `GRL.004`.
Quando o provider já recebe o `Telemetry` do framework, a auto-instrumentação `langfuse.openai` é desabilitada para evitar uma segunda observation fora do contrato central.
O caminho relativo configurado em `OBSERVABILITY_CODE_MAPPING_PATH` é procurado no diretório corrente e nos roots de importação Python, permitindo iniciar o Uvicorn fora do diretório raiz do agente sem perder o mapping.
## Compatibilidade automática do framework
A partir desta versão, o framework possui um registry default interno (`agent_framework/config/observability_mapping.yaml`) carregado mesmo quando o agente não possui `OBSERVABILITY_CODE_MAPPING_*`. O arquivo do agente, quando habilitado, funciona como overlay. Isso permite substituir somente a versão do framework em agentes legados sem mudar a taxonomia GRL nem as decisões históricas dos rails.
Veja também `agent_framework_oci/libs/agent_framework/docs/OBSERVABILITY_DEFAULT_OVERLAY_COMPATIBILITY.md`.

View File

@@ -1,43 +0,0 @@
# Observability Contract Registry
`config/observability_mapping.yaml` é a única tabela usada pelo framework para duas responsabilidades relacionadas ao contrato externo:
1. traduzir nomes/códigos semânticos para labels exigidos pela observabilidade do cliente;
2. preservar ações legadas de guardrails (`retry`, `handover`, etc.) sem hardcode de nomes no Python.
A sintaxe v1 continua válida:
```yaml
mappings:
guardrail.dlex_in: GRL.004
```
A forma rica adiciona `action` e `aliases`:
```yaml
mappings:
guardrail.revprec:
action: retry
aliases: [REVPREC, TIM_REVPREC]
```
`label` é opcional. Quando ausente, o nome de observabilidade não é renomeado. `action` também é opcional.
## Precedência de ação
Para uma decisão negada, o framework usa:
1. `metadata.terminal_action` retornado pelo rail;
2. `on_deny` do `guardrails.yaml`;
3. `action` resolvida pelo `observability_mapping.yaml`;
4. `BLOCK` como fallback fail-safe.
Isso mantém compatibilidade com rails internos e externos sem que `OutputSupervisor` ou `ParallelRailExecutor` conheçam nomes como `REVPREC`, `CMP`, `SCO`, `GND`, `ATH` ou `HUMAN`.
## Aliases
Uma entrada `guardrail.revprec` é automaticamente resolvida também por `REVPREC`. Aliases explícitos permitem associar nomes externos ou históricos, por exemplo `TIM_REVPREC`.
## Compatibilidade
Mappings escalares continuam funcionando sem alteração. Agentes que não habilitam o mapper continuam em passthrough e usam `BLOCK` para negações sem ação explícita.

View File

@@ -1,26 +0,0 @@
# Correção do merge Default + Overlay de Observabilidade
## Problema
O default do framework estava ativo, porém em alguns caminhos o overlay do agente não era carregado. O efeito observado no Langfuse era `GRL.DLEX_IN`/`GRL.TOX` (default) em vez de `GRL.004`/`GRL.005` (Contas).
## Correção
O framework agora monta um único registry efetivo antes de qualquer resolução:
1. carrega `agent_framework/config/observability_mapping.yaml`;
2. localiza o overlay do agente;
3. faz merge por chave canônica, com o agente sobrescrevendo o default;
4. reconstrói os aliases somente depois do merge;
5. usa esse único registry em LLM provider, Telemetry, Analytics, OutputSupervisor e ParallelRailExecutor.
## Descoberta do overlay
Além de `OBSERVABILITY_CODE_MAPPING_PATH`, o framework autodetecta `config/observability_mapping.yaml` no cwd e nos roots de importação Python. O arquivo default empacotado do framework é excluído dessa descoberta.
Assim um agente com arquivo convencional de overlay não depende de alterar seu launcher ou `.env` para que a customização seja aplicada.
## Resultado esperado no Contas
- `guardrail.dlex_in` -> `GRL.004`
- `guardrail.tox` -> `GRL.005`
- componentes não sobrescritos continuam herdando o default do framework.
## Compatibilidade
Agentes antigos sem overlay continuam usando apenas o default do framework e preservam a taxonomia/ações históricas.

View File

@@ -1,83 +0,0 @@
# OutputSupervisor sem taxonomia contratual hardcoded
## Objetivo
O `OutputSupervisor` do framework trabalha somente com eventos semânticos e ações de runtime. Códigos contratuais externos/numerados pertencem exclusivamente ao `ObservabilityCodeMapper` configurado pelo agente/deployment.
## Eventos internos
Exemplos de eventos internos:
```text
guardrail.output_supervisor.started
guardrail.result.allow
guardrail.result.block
guardrail.result.retry
guardrail.output.<rail>.completed
guardrail.output_supervisor.completed
```
Se um cliente exigir códigos próprios, configure `config/observability_mapping.yaml`. O supervisor não conhece a taxonomia externa.
## Ação quando um rail nega
O framework não decide mais a ação procurando nomes específicos de rails. A ação pode vir do próprio resultado:
```python
metadata={"terminal_action": "retry"}
```
ou do YAML:
```yaml
output:
- code: MY_VALIDATION
enabled: true
on_deny: retry
```
Valores suportados são os valores de `RailAction`, como `block`, `retry` e `handover`.
## Remediação por rewrite
Rewrite também é uma capacidade genérica. O rail/policy declara a remediação:
```yaml
output:
- code: MY_WORDING_POLICY
enabled: true
on_block:
type: rewrite
max_attempts: 1
prompt_id: FALLBACK
profile_name: grl
component_name: guardrail.wording.rewrite
```
O supervisor não verifica se o código é `FRASEOLOGIA` ou qualquer outro nome. Um guardrail externo do agente pode usar exatamente o mesmo contrato.
## Mensagens de UX
Mensagens de fallback/handover pertencem ao agente:
```yaml
output_supervisor:
max_retries: 3
fallback_message: "..."
handover_message: "..."
```
Assim o framework não precisa conhecer idioma, marca ou fraseologia do atendimento.
## Contas
O Contas preserva seu comportamento atual:
- `TIM_REVPREC` declara `terminal_action=retry` no próprio rail externo;
- `CMP` está configurado com `on_deny: retry`;
- `TIM_FRASEOLOGIA`, quando habilitado, declara remediação `rewrite` no agente;
- textos de fallback/handover ficam no `config/guardrails.yaml` do Contas.
## Compatibilidade
Rails que retornam apenas `allowed=false` e não declaram policy continuam em `block`, que é o fail-closed genérico. Não há mais inferência de ação pelo nome do rail.

View File

@@ -1,112 +0,0 @@
# Pente-fino de paridade — Contas original x Contas migrado
## Escopo
Comparação funcional e arquitetural das 18 capabilities solicitadas, tomando como fonte de verdade o projeto Contas original e preservando, no migrado, as responsabilidades genéricas do `agent_framework_oci` (roteamento, confirmação, pause/resume, RAG, memória, observabilidade e política de tools).
Tools/capabilities avaliadas:
`consultar_faturas`, `consultar_plano`, `invoice_explanation`, `buscar_informacao`, `consultar_vas`, `consultar_historico_vas`, `cancelar_vas_avulso`, `tratar_vas_estrategico`, `validar_contestacao`, `contestar_cobranca`, `finalizar_atendimento`, `consultar_status_solicitacao`, `enviar_sms`, `recuperar_fatura_pdf`, `pro_rata`, `termino_desconto`, `valor_divergente`, `retomar_workflow`.
## Resultado executivo
Depois das correções deste pente-fino, as 18 capabilities estão expostas no MCP e habilitadas no registry do agente. Os workflows conversacionais principais foram preservados do original. Os YAMLs `buscar_fatura`, `buscar_informacao`, `cancelamento_vas_avulso`, `finalizar_atendimento`, `invoice_explanation`, `pro_rata`, `termino_desconto`, `valor_divergente` e `vas_estrategico` permanecem equivalentes ao original. `contestacao_tool` contém uma diferença intencional de segurança: se a validação financeira falhar, o fluxo termina antes de SMS/contrato/SR.
A suíte completa do projeto após as mudanças executa **715 testes com sucesso**.
## Paridade por capability
| Capability | Fonte/semântica no original | Situação após pente-fino | Ação tomada |
|---|---|---|---|
| `consultar_faturas` | `complete_invoices` + prefetch de `bill_pdf` + resumo semântico | Corrigida | Mantida a API de Complete Invoices e restaurados `invoice_amount`, `invoice_amount_open`, período e emissão a partir do PDF, sem inventar campo no backend. |
| `consultar_plano` | Evidência da própria fatura/billing analysis | OK | Extração determinística de seções `Plano/Planos`; não usa RAG nem inferência livre da LLM. |
| `invoice_explanation` | Workflow v2 + evidência da fatura + capability LLM de reescrita + pause | Corrigida | `invoice_detail` e resumo semântico agora chegam ao workflow; composição volta a ser da LLM do framework; `await_user_input=True` restaurado. |
| `buscar_informacao` | Tool RAG ativa (`queries` e `query` legado) | Corrigida arquiteturalmente | Reexposta como façade MCP compatível. Não duplica RAG no domínio: retorna `requires_rag` e delega ao `RagService` do framework. Suporta `queries[]` e `query`. |
| `consultar_vas` | Consulta de VAS ativos | OK | Mantida integração direta e resolução de domínio para os fluxos que precisam classificar item. |
| `consultar_historico_vas` | Histórico de VAS/serviços | OK | Mantido contrato da integração e normalização usada pelo cancelamento. |
| `cancelar_vas_avulso` | Tool ativa, somente avulso, confirmação, cancelamento + contestação automática | OK | Confirmação permanece no framework; preflight resolve nome/classe contra a fatura; workflow composto preserva cancelamento + contestação e protocolos. |
| `tratar_vas_estrategico` | `vas_estrategico`, bundle/estratégico | OK | Alias semântico migrado para `tratar_vas_estrategico`; workflow v3 preservado; redirecionamento automático evita cancelar estratégico como avulso. |
| `validar_contestacao` | Não existia como tool pública; regras CVAL existiam na execução | Corrigida | A pré-validação agora usa a mesma `validate_contestation_items` da execução financeira; deixa de aprovar algo que seria bloqueado depois. Usa o valor pedido pelo cliente, não o `resolved_value` da fatura. |
| `contestar_cobranca` | Workflow/ações de contestação e Conta Certa | OK + hardening | Workflow preservado e CVAL fail-closed. Adicionado edge de segurança para não continuar com SMS/contrato/SR após falha financeira. |
| `finalizar_atendimento` | Tool ativa; `status` obrigatório e regras estritas de finalização | Corrigida | `status` voltou a ser requisito explícito e é extraído genericamente pelo framework com enum semântico; `erro_falha_sistema` continua reservado ao sistema. |
| `consultar_status_solicitacao` | Integração de status SR usada internamente | Corrigida | Removido mapeamento incorreto `interaction_key -> protocol` (interaction_key é identidade da interação/mensagem, não protocolo). Protocolo é extraído da fala ou recuperado de aliases do contexto de workflow. |
| `enviar_sms` | Ação de integração usada pelos workflows | OK | Mantida integração TIM; continua sem regra de negócio dentro do framework. |
| `recuperar_fatura_pdf` | SecurePDF/invoice recover no original | Corrigida | Agora participa do `InvoiceContextService` para obter `customer_id` antes do SecurePDF quando não vier explicitamente. |
| `pro_rata` | Workflow v3; regra explícita de exatamente 2 planos e `has_plano_controle` | Corrigida | O MCP deriva os planos da evidência do Bill PDF/billing analysis, deduplica repetição DANFE/linha e falha fechado se não houver exatamente dois planos. `has_plano_controle` é determinístico. |
| `termino_desconto` | Capability/backend existente; versão migrada havia cristalizado causa sem evidência | Corrigida + hardening | A causa só é afirmada quando backend/mock fornece evidência causal explícita de desconto/promoção. Sem essa prova, responde que o motivo não está disponível; parcelas e texto do cliente não viram fato. |
| `valor_divergente` | Capability/backend existente | Corrigida | Restaurada a semântica original de alteração no valor do plano por linha, em vez de texto genérico sobre billing analysis. |
| `retomar_workflow` | Resume era interno ao runtime/executor original | OK arquiteturalmente | Exposto como façade genérica do `WorkflowRuntime.aresume`; não replica estado conversacional dentro do domínio Contas. |
## Correções relevantes encontradas
### 1. Contexto de fatura
O original não obtinha o valor total diretamente de `complete_invoices`. Ele construía um contexto enriquecido a partir do PDF parseado. A migração já possuía parser e `InvoiceContextService`, mas faltava publicar integralmente o resumo semântico. Foi restaurada a cadeia:
`Complete Invoices -> invoice/customer id -> Bill PDF -> parser -> total_geral -> invoice_amount/invoice_amount_open`.
### 2. `invoice_explanation`
O YAML migrado preservava o `pause`, mas a action migrada não retornava `await_user_input=True`, ao contrário do original. Isso tornava a condição de pause falsa. Também havia sido eliminada a etapa de composição LLM específica. Agora a action retorna o gate de pause e uma diretiva `requires_llm_composition`, mantendo a LLM no framework, não no domínio.
### 3. `buscar_informacao`
A route ainda referenciava `buscar_informacao`, mas a tool estava `enabled: false` e nem era exposta pelo MCP. Isso criava uma discrepância entre o contrato original e a configuração migrada. A façade foi restaurada sem reintroduzir RAG customizado no Contas.
### 4. `pro_rata`
O schema/descrição original exigia **exatamente dois planos**. A migração aceitava `planos=[]` e podia afirmar pró-rata mesmo sem evidência. Agora os planos são derivados da fatura e a operação retorna `NOT_APPLICABLE` quando a evidência não comprova exatamente dois planos.
### 5. Pré-validação de contestação
`validar_contestacao` apenas resolvia o item e retornava `eligible=true`; a validação CVAL real só acontecia depois, já no workflow transacional. Agora a pré-validação e a execução usam a mesma regra financeira, evitando confirmação para uma operação que será inevitavelmente bloqueada.
### 6. Status de solicitação
O mapper usava `interaction_key` como `protocol`. Isso é semanticamente incorreto: `interaction_key` identifica a interação do framework. O protocolo agora vem da mensagem ou de campos de protocolo existentes no contexto transacional (`protocol_number`, `protocolo_id`, `contestacao_protocol`, `cancelamento_vas_protocol`).
## Arquivos principais alterados
- `contas_mcp/servers/contas_mcp_server/main.py`
- `app/domain/contas/service.py`
- `app/domain/contas/workflow_actions.py`
- `app/domain/contas/invoice_context.py` (correção anterior da Opção A, mantida)
- `config/tools.yaml`
- `config/mcp_parameter_mapping.yaml`
- `workflows/contestacao_tool.v2.yaml` (hardening já presente)
- `tests/migration/test_requested_tools_parity_pente_fino.py`
- testes de invoice context previamente adicionados/mantidos
## Testes de regressão adicionados
O novo arquivo `tests/migration/test_requested_tools_parity_pente_fino.py` verifica, entre outros pontos:
- exposição e habilitação das 18 tools;
- façade framework-native de RAG;
- consulta determinística de plano;
- propagação de `invoice_detail` e resumo semântico;
- composição LLM + pause de `invoice_explanation`;
- regra de exatamente dois planos em pró-rata;
- CVAL na pré-validação e rejeição de valor acima da cobrança;
- semântica de término de desconto e valor divergente;
- contratos diretos de VAS, histórico, status, SMS e PDF;
- ausência do mapeamento incorreto `interaction_key -> protocol`;
- obrigatoriedade/classificação de status na finalização.
## Resultado dos testes
```text
715 passed
```
A suíte completa disponível no pacote foi executada, não apenas os testes novos.
## Histórico autoritativo de descontos
A capability `termino_desconto` não deve deduzir a causa da retirada de desconto a partir de parcelas, ausência do item ou texto do cliente. Foi introduzida a integração MCP `consultar_historico_descontos`, com mock em `app/domain/contas/fixtures/discount_history.json`.
O serviço retorna fatos estruturados (`discount_name`, `plan_name`, `previous_value`, `current_value`, `start_date`, `end_date`, `discount_status`, `termination_reason` e `termination_reason_description`). O workflow `termino_desconto` chama essa fonte obrigatoriamente antes de compor a resposta. Se a causa não estiver explícita, mantém `epistemic_status=insufficient_evidence`; quando a causa está presente, usa `epistemic_status=grounded_fact`.
A referência temporal do mock é explícita: `current_value` representa a situação contratual em `as_of_date`, enquanto `last_billed_discount_value` representa o desconto aplicado no último período faturado (`last_billed_period`). Isso evita tratar como contradição o caso em que uma fatura referente a período anterior ainda contém o desconto, embora o benefício já esteja encerrado na data contratual corrente.
O mock serve somente para ilustrar o contrato que deverá ser substituído pela integração real. A resposta ao cliente é composta no agente; o serviço não retorna frase pronta.

View File

@@ -1,302 +0,0 @@
# Relatório de Correções — Agent Framework OCI + Contas
Data: 2026-08-28
Base analisada: `agent_contas_oci_template (6).zip`
## 1. Objetivo
Este trabalho tratou as frentes técnicas identificadas a partir do comparativo de 30 replays e, principalmente, dos contratos de regressão já existentes no próprio projeto. A separação arquitetural foi preservada:
- **Framework**: lifecycle transacional, coleta genérica de parâmetros, confirmação, snapshot, roteamento/continuidade, guardrails e infraestrutura horizontal.
- **Agent Contas / domínio / MCP**: semântica TIM, prompts voltados ao cliente, contrato das capabilities, evidência de fatura, regras de contestação, pró-rata e mapeamentos de integração.
Nenhuma regra TIM foi movida para o core do framework.
## 2. Correções realizadas no framework
### 2.1 Coleta de parâmetros sem expor nomes internos
**Problema**
O runtime possuía um pequeno dicionário hardcoded para `order_id`, `reason` e `customer_id` e, para qualquer outro parâmetro, podia produzir o nome técnico convertido para texto. Isso explica respostas da família `informe subject` apontadas no relatório.
**Correção**
O framework agora usa metadados declarados pelo agente em `args_schema`:
- `user_prompt`: pergunta exata voltada ao cliente, com maior prioridade;
- `label`: rótulo amigável opcional;
- `description`: fallback semântico;
- sem metadados: pergunta neutra que **não expõe o nome técnico**.
Além disso, o framework pergunta **um parâmetro por vez**, embora o extrator LLM continue capaz de consumir vários valores espontaneamente informados no mesmo turno.
**Resultado arquitetural**
O framework continua sem saber o significado de `subject`, `valor`, `order_id` etc. A semântica pertence ao agente.
### 2.2 Snapshot imutável da confirmação
**Problema**
Havia `pending_tool_call` e `active_transaction`, mas não existia um snapshot separado e explícito que representasse exatamente a operação apresentada ao usuário no momento da confirmação.
**Correção**
Foi introduzido `confirmation_snapshot`, contendo:
- `transaction_id`;
- `tool_name`;
- cópia dos `arguments`;
- `started_from_intent`.
Ao entrar em `AWAITING_CONFIRMATION`, o snapshot é congelado. Um `sim` executa **esse snapshot**, mesmo que `active_transaction`, `pending_tool_call` ou outro contexto seja alterado depois. Ao concluir/cancelar a transação, o snapshot operacional é limpo.
**Benefício**
Garante o contrato:
> confirmar = executar exatamente tool + parâmetros que estavam congelados quando a confirmação foi solicitada.
### 2.3 Itens do framework já presentes nesta versão e apenas revalidados
Não foram duplicadas correções que já estavam na base recebida:
- extração LLM de parâmetros transacionais;
- precedência de confirmação explícita;
- `transaction_interruption=intent_shift`;
- encerramento/limpeza de transações `COMPLETED`, `FAILED`, `CANCELLED`, `BLOCKED`, `OUT_OF_SCOPE`;
- route stickiness sem reaproveitar transação terminal;
- replay pós-finalização sem reabrir atendimento;
- validação direta de `expected_protocols` no CMP;
- isolamento do contexto operacional dos guardrails após intent shift no Contas.
## 3. Correções realizadas no Agent Contas / MCP
### 3.1 Prompts declarativos dos parâmetros
Foram adicionados `user_prompt` às capabilities transacionais:
- `cancelar_vas_avulso.subject``Qual serviço você deseja cancelar?`
- `tratar_vas_estrategico.subject``Qual serviço ou benefício você deseja tratar?`
- `validar_contestacao.subject``Qual cobrança ou item você não reconhece?`
- `validar_contestacao.valor``Qual é o valor da cobrança?`
- `contestar_cobranca.subject``Qual cobrança ou item você deseja contestar?`
- `contestar_cobranca.valor``Qual é o valor da cobrança que você deseja contestar?`
Assim, a linguagem de atendimento fica no domínio e o framework apenas executa o contrato.
### 3.2 Capability `buscar_informacao` restaurada sem duplicar RAG
A capability voltou a existir no registry/MCP para manter paridade de contrato, mas não reimplementa recuperação no domínio.
Ela devolve um contrato explícito:
- `requires_rag=true`;
- `source=agent_framework.rag`;
- `rag_queries=[...]`.
Portanto, a API antiga é preservada e o RAG continua sendo responsabilidade do framework.
### 3.3 `invoice_explanation` preserva evidência suficiente para composição
O retorno passa a preservar também:
- `invoice_detail`;
- `invoice_amount`;
- `invoice_period`;
- `invoice_emissao`.
A action `formatar_invoice_explanation` agora sinaliza:
- `await_user_input=true`;
- `requires_llm_composition=true`;
- `response_instruction` de composição grounded;
- preservação da pergunta `Com essa explicação, sanei sua dúvida?`.
### 3.4 Pró-rata determinístico e fail-closed
Foram restaurados helpers de preparação do pró-rata:
- derivação determinística dos planos a partir do PDF parseado;
- uso da visão contratual por linha, evitando confundir DANFE com plano consolidado;
- identificação de plano controle;
- exigência de **exatamente dois planos**;
- falha fechada com `requires_exactly_two_plans` quando o contrato não é atendido.
Nenhum LLM é usado nessa decisão.
### 3.5 CVAL aplicado também na pré-validação
`validar_contestacao` deixou de apenas aceitar o item após o preflight e passou a executar a mesma validação CVAL usada antes do efeito financeiro.
A validação usa:
- item resolvido;
- **valor originalmente solicitado pelo cliente**;
- evidência de `billing_analysis`;
- `validation_log` estruturado.
Valor solicitado acima do valor comprovado é bloqueado com `reason=CVAL` e erro `valor_ajuste_maior_que_item`.
Foi corrigido também um teste de regressão inconsistente: ele exigia aprovar R$ 50 para um item comprovado em R$ 10, ao mesmo tempo em que dizia proteger a regra “valor não pode exceder o item”. O caso positivo foi ajustado para R$ 10; a implementação não foi enfraquecida para satisfazer uma expectativa insegura.
### 3.6 Grounding de término de desconto e valor divergente
`termino_desconto` foi endurecido para não transformar uma hipótese de negócio em fato. O workflow só informa causa de retirada/término quando a evidência de backend/mock contém um campo causal explicitamente associado a desconto/promoção (por exemplo `discount_reason`, `terminationReason`, status de desconto/promoção encerrado ou data de término registrada). Contadores como `1/12`, `8/12` ou `12/12`, ausência de desconto na fatura e o próprio texto do cliente não são tratados como prova de expiração.
Quando a causa não está disponível, a resposta informa que os dados existentes não registram o motivo, sem afirmar fim de fidelidade ou expiração promocional.
`valor_divergente` preserva a semântica de alteração do valor do plano e referência segura ao final da linha, conforme contrato de regressão.
### 3.7 Status de solicitação não usa `interaction_key` como protocolo
Foi removido:
`interaction_key -> protocol`
O protocolo agora é extraído explicitamente da mensagem, impedindo que `message_id`/`interaction_key` seja tratado como protocolo de atendimento.
### 3.8 Finalização exige status explícito
`finalizar_atendimento` agora declara `status` em `requires`, e o mapping possui extração explícita do campo. Isso preserva o contrato de domínio e evita finalização sem estado definido.
### 3.9 Prompt de billing mais grounded
O `FaturasAgent` recebeu regra explícita para não transformar ausência de evidência em hipótese factual. Sem evidência, ele não pode afirmar como causa:
- fim de promoção;
- perda de elegibilidade;
- alteração de consumo;
- reajuste tarifário;
- mudança de plano.
Isso endereça diretamente o comportamento observado no comparativo, em que hipóteses eram apresentadas como explicação.
### 3.10 Prompt de suporte não simula efeitos de lifecycle
O `SuporteContasAgent` foi reforçado para não anunciar em texto livre:
- transferência;
- encerramento;
- protocolo;
- sucesso operacional.
Resultados terminais devem refletir apenas o estado/tool atual. Handoff e finalização continuam controlados pela orquestração.
## 4. Fontes alterados
### Framework
| Arquivo | Alteração |
|---|---|
| `agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py` | Prompt declarativo de parâmetros; remoção de labels hardcoded; pergunta neutra sem leak; `confirmation_snapshot`; execução a partir do snapshot; limpeza do snapshot no lifecycle. |
| `agent_framework_oci/libs/agent_framework/build/lib/agent_framework/runtime/agent_runtime.py` | Sincronizado com o source para manter o artefato de build consistente. |
| `agent_framework_oci/tests/test_transactional_tool_flow.py` | Regressões para `user_prompt`, ausência de leak de nome técnico e confirmação por snapshot imutável. |
### Agent Contas / MCP
| Arquivo | Alteração |
|---|---|
| `config/tools.yaml` | `user_prompt` dos parâmetros; capability `buscar_informacao`; `finalizar_atendimento.status` obrigatório. |
| `config/mcp_parameter_mapping.yaml` | Protocolo deixa de vir de `interaction_key`; extração explícita de `protocol`; extração explícita de `status` na finalização. |
| `config/prompts/billing.yaml` | Proibição explícita de hipóteses causais sem evidência. |
| `config/prompts/support.yaml` | Não simular handoff/finalização/protocolo; tratamento terminal grounded. |
| `app/domain/contas/service.py` | `buscar_informacao`; preservação de `invoice_detail`, amount, period e emissão em `invoice_explanation`. |
| `app/domain/contas/workflow_actions.py` | Metadados de composição LLM/await no invoice explanation; semântica de `termino_desconto` e `valor_divergente`. |
| `contas_mcp/servers/contas_mcp_server/main.py` | Registro `buscar_informacao`; helpers de pró-rata; preparação fail-closed; CVAL na pré-validação; dispatch das novas/restauradas capabilities. |
| `tests/migration/test_framework_agent_gap_fixes.py` | Novos contratos de regressão framework × agente. |
| `tests/migration/test_requested_tools_parity_pente_fino.py` | Correção do caso positivo CVAL inconsistente (R$50 → R$10 comprovados). |
## 5. Validação executada
### Framework — testes focados das frentes alteradas
Resultado:
`40 passed`
Incluiu:
- transaction tool flow;
- confirmação voltada ao cliente;
- extração LLM/prevalência de parâmetros;
- route stickiness / intent shift;
- novos testes de snapshot e user-facing parameter contract.
### Agent Contas — regressão completa de migração
Resultado final:
`729 passed`
Antes das correções, o `test_requested_tools_parity_pente_fino.py` expunha 11 falhas. Após as correções:
`14 passed` nesse arquivo e `729 passed` em toda `tests/migration`.
### Suíte completa do framework
Resultado observado na árvore corrigida:
- `225 passed`
- `10 failed`
Os mesmos 10 casos foram executados contra o ZIP original recebido e falham da mesma forma. Portanto são **falhas preexistentes e não introduzidas por este patch**. Estão concentradas em:
- compatibilidade de double de LLM em um teste unitário;
- checkpoint repository/recovery;
- compact telemetry Langfuse legado;
- transactional workflow unit tests;
- dois testes estáticos que procuram um layout de `agent_template_backend` inexistente nesse caminho.
Esses itens não pertencem às frentes do comparativo tratadas neste patch e não foram mascarados.
## 6. Relação com o relatório comparativo
### Problemas do relatório atacados diretamente
- nomes internos de parâmetros na fala;
- coleta transacional sem contrato amigável;
- confirmação sem snapshot explícito;
- risco de reinterpretar argumentos depois do pedido de confirmação;
- explicação de cobrança baseada em hipótese sem evidência;
- gaps de capability/paridade MCP já formalizados pelos testes do projeto;
- pró-rata sem preparação determinística completa;
- pre-validation/CVAL incompleta;
- confusão entre identificador de interação e protocolo;
- finalização sem `status` obrigatório.
### Problemas que já estavam corrigidos nesta versão recebida
- intent shift durante transação;
- limpeza de transação terminal;
- replay pós-finalização;
- barge-in pós-finalização no framework de interrupção;
- `expected_protocols`/CMP;
- contexto histórico de transação anterior nos guardrails do Contas.
## 7. Pontos que continuam sendo política de negócio do Contas
Não foram movidos para o framework, de propósito:
- escada comercial de retenção TIM;
- quando exatamente transferir para humano após retenção;
- primeira/segunda ocorrência de fora de escopo;
- escalonamento jurídico/Anatel específico TIM;
- política de ressarcimento em dobro;
- interpretação de conjuntos de cobranças como “nenhuma delas/todas”;
- regras específicas de VAS avulso/estratégico e ações comerciais.
Esses comportamentos devem ser implementados/testados no domínio Contas quando os cenários executáveis correspondentes estiverem disponíveis. O pacote recebido não contém os 30 YAMLs de replay citados no PDF, portanto este relatório **não afirma** que os 30 replays agora passam; afirma apenas os resultados das suítes efetivamente presentes e executadas no pacote.
## 8. Conclusão
A principal correção estrutural foi tornar a fronteira mais clara:
- o **framework** controla coleta, lifecycle e confirmação sem expor nomes internos e sem reinterpretar o que foi confirmado;
- o **agente Contas** fornece a linguagem de negócio e os contratos/evidências específicos;
- o **MCP Contas** mantém capabilities e validações determinísticas de domínio sem absorver responsabilidades de conversa/RAG do framework.
A regressão do Contas presente no projeto ficou integralmente verde (`729 passed`).
### Serviço MCP de histórico de descontos
Foi adicionada a tool interna `consultar_historico_descontos` para representar a fonte autoritativa de status e término de descontos. O mock está em `app/domain/contas/fixtures/discount_history.json` e a implementação em `contas_mcp/servers/contas_mcp_server/discount_history_service.py`.
`termino_desconto` consulta esse serviço obrigatoriamente e só verbaliza uma causa quando `termination_reason`, `termination_reason_description` ou outro campo causal explicitamente permitido estiver presente. Códigos técnicos permanecem em metadados; a resposta usa a descrição legível do sistema. Sem causa explícita, o fluxo continua fail-closed.
O mock também distingue explicitamente a **situação contratual na data de referência** da **última fatura emitida**. No cenário atual, `current_value=0` significa valor contratual do desconto em `as_of_date=2025-11-20`; a última fatura cobre `14/10 a 13/11` e ainda registra R$ 80,00 de desconto. Isso é temporalmente consistente: o desconto estava vigente no período faturado e aparece como encerrado na situação contratual de 20/11/2025. Os campos `last_billed_discount_value`, `last_billed_period`, `last_invoice_issue_date` e `current_value_reference` documentam essa diferença.

View File

@@ -1,423 +0,0 @@
# Relatório técnico — políticas alternativas de operação por linha
## 1. Objetivo
O Agent Contas possui duas políticas alternativas para controlar operações em uma linha (MSISDN) diferente da linha identificada/autenticada no início do atendimento.
A implementação permanece no **Agent Contas/MCP Contas**, sem regra TIM hardcoded no core do `agent_framework_oci`.
A política ativa entregue no projeto continua sendo a **ALT1 — somente a linha autenticada**.
A ALT2 foi evoluída para não inferir autorização a partir de fatura, billing ou texto do cliente. Ela depende de uma fonte explícita de autorização: a nova tool MCP mock `consultar_linhas_autorizadas`.
## 2. Políticas disponíveis
### 2.1 ALT1 — `authenticated_line_only` — PADRÃO
Fonte:
```text
contas_mcp/servers/contas_mcp_server/line_policy_alt1.py
```
Comportamento:
- a linha operacional continua sendo a linha identificada pelo `business_context` da chamada;
- uma linha citada em texto livre **não substitui** a identidade da sessão;
- se o cliente mencionar explicitamente outra linha — número completo ou referência como `final 4321` — a execução é bloqueada antes de qualquer operação de domínio;
- o bloqueio é terminal para o turno e interrompe as tools seguintes;
- a mensagem devolvida é:
```text
Por segurança, este atendimento só permite consultar ou realizar operações na linha identificada na chamada. Não posso usar outra linha informada na conversa.
```
Exemplo:
```text
linha autenticada: 11999999999
cliente: "quero cancelar o streaming do número da minha esposa, final quatro três dois um"
resultado:
LINE_POLICY_BLOCKED / other_line_not_allowed
nenhuma consulta/cancelamento é executado para a outra linha
```
### 2.2 ALT2 — `authorized_related_lines`
Fonte:
```text
contas_mcp/servers/contas_mcp_server/line_policy_alt2.py
```
Comportamento:
- a linha autenticada continua sendo a origem de confiança;
- uma outra linha só pode ser usada se for retornada pelo serviço explícito `consultar_linhas_autorizadas`;
- referências como `final 4321` são resolvidas somente contra as linhas autorizadas retornadas por esse serviço;
- se houver exatamente uma correspondência, ela vira o `effective_msisdn` da operação;
- se não houver correspondência, a operação é bloqueada;
- se houver mais de uma correspondência, o fluxo exige esclarecimento;
- se o serviço de linhas autorizadas falhar, o ALT2 opera em **fail-closed**: somente a linha autenticada permanece autorizada;
- a presença de um MSISDN em `invoice_detail`, `billing_analysis` ou outra evidência de cobrança **não concede autorização operacional**;
- um número pronunciado pelo cliente também **não concede autorização**.
Exemplo:
```text
linha autenticada: 11999999999
consultar_linhas_autorizadas retorna:
- 11999999999 (titular)
- 11988884321 (dependente autorizado)
cliente: "quero cancelar o TIM Fashion da linha final 4321"
resultado ALT2:
requested reference = 4321
effective_msisdn = 11988884321
operação pode prosseguir nessa linha
```
## 3. Novo serviço MCP mock — `consultar_linhas_autorizadas`
### 3.1 Objetivo
Foi criada uma tool MCP side-effect-free para representar a integração que, em produção, deve consultar um serviço de identidade/conta e responder **quais linhas o atendimento autenticado está autorizado a operar**.
Tool:
```text
consultar_linhas_autorizadas
```
Registro MCP:
```text
contas_mcp/servers/contas_mcp_server/main.py
```
Implementação mock:
```text
contas_mcp/servers/contas_mcp_server/authorized_lines_service.py
```
Fixture mock:
```text
app/domain/contas/fixtures/authorized_lines.json
```
### 3.2 Contrato de entrada
A consulta parte da identidade já autenticada no atendimento. O cliente não informa qual linha deve ser autorizada.
Exemplo:
```json
{
"msisdn": "11999999999",
"customer_key": "11999999999",
"contract_key": "3000131180"
}
```
O `msisdn` acima é a linha autenticada/original da chamada.
### 3.3 Contrato de saída mock
```json
{
"success": true,
"status": "SUCCESS",
"source": "mock",
"authenticated_msisdn": "11999999999",
"authorized_lines": [
{
"msisdn": "11999999999",
"relationship": "titular",
"status": "ACTIVE",
"authorized": true
},
{
"msisdn": "11988884321",
"relationship": "dependente",
"status": "ACTIVE",
"authorized": true
}
],
"authorized_msisdns": [
"11999999999",
"11988884321"
]
}
```
### 3.4 Por que existe uma tool MCP separada
O objetivo é deixar explícita a arquitetura de produção:
```text
identidade autenticada da chamada
consultar_linhas_autorizadas
serviço legado/CRM/IAM/conta
lista de linhas realmente autorizadas
line_policy_alt2
resolve referência conversacional
0 matches → bloqueia/clarifica
1 match → effective_msisdn
>1 matches → clarifica
```
A autorização não pertence ao LLM. O LLM/text extractor pode interpretar `final 4321`, mas não decide se `4321` é uma linha autorizada.
### 3.5 Comportamento em produção
O arquivo `authorized_lines_service.py` é propositalmente um mock de referência. Em produção, ele deve ser substituído por um adapter que consulte o serviço corporativo responsável pela relação titular/dependentes/linhas autorizadas.
O contrato recomendado deve preservar pelo menos:
```text
success
authenticated_msisdn
authorized_lines[].msisdn
authorized_lines[].status
authorized_lines[].authorized
authorized_lines[].relationship
authorized_msisdns
```
Se a integração real falhar ou não puder provar a autorização da outra linha, o comportamento esperado do ALT2 é fail-closed.
## 4. Fluxo ALT2 atualizado
O fluxo completo ficou:
```text
mensagem do cliente
extrai requested_line_reference
ex.: suffix=4321
business_context mantém 11999999999
MCP detecta ALT2 ativo
consultar_linhas_autorizadas(11999999999)
authorized_lines_evidence
line_policy_alt2
resolve 4321 somente contra authorized_lines_evidence
effective_msisdn = 11988884321
só então a tool/workflow de negócio é executada
```
A ALT2 não usa mais `invoice_detail`, `billing_analysis` ou `complete_invoices_payload` como fonte de **autorização** de linha.
## 5. Política ativa
O MCP importa sempre:
```text
contas_mcp/servers/contas_mcp_server/line_policy.py
```
No pacote entregue, `line_policy.py` é uma cópia exata de `line_policy_alt1.py`.
Portanto, **o comportamento corrente permanece bloqueando operações em outra linha**.
O `/health` informa a política carregada:
```json
{
"line_policy": "authenticated_line_only",
"line_policy_description": "Somente a linha identificada/autenticada na chamada pode ser consultada ou alterada."
}
```
## 6. Como ativar ALT1
Forma recomendada:
```bash
python scripts/select_line_policy.py alt1
```
Depois reinicie o backend/MCP Server.
Linux/macOS:
```bash
cp contas_mcp/servers/contas_mcp_server/line_policy_alt1.py \
contas_mcp/servers/contas_mcp_server/line_policy.py
```
PowerShell:
```powershell
Copy-Item `
contas_mcp/servers/contas_mcp_server/line_policy_alt1.py `
contas_mcp/servers/contas_mcp_server/line_policy.py -Force
```
## 7. Como ativar ALT2
```bash
python scripts/select_line_policy.py alt2
```
Depois reinicie o backend/MCP Server.
Ao iniciar com ALT2, o MCP passa a consultar automaticamente `consultar_linhas_autorizadas` quando houver uma referência explícita a linha no turno.
Não é necessário inserir manualmente:
```python
context["authorized_msisdns"] = [...]
```
nem:
```python
args["authorized_msisdns"] = [...]
```
A lista vem do serviço MCP de autorização.
## 8. Como alterar o mock para testes
Para ilustrar outra linha autorizada, edite apenas:
```text
app/domain/contas/fixtures/authorized_lines.json
```
Exemplo:
```json
{
"msisdn": "11977771234",
"relationship": "dependente",
"status": "ACTIVE",
"authorized": true
}
```
Não altere `line_policy_alt2.py` para cadastrar linhas.
Esse desenho deixa claro que a política apenas **consome autorização**; ela não é o cadastro das linhas autorizadas.
## 9. Abrangência
A política é aplicada no ponto único `_invoke()` do MCP Contas antes da execução de domínio. Dessa forma cobre as tools/serviços baseados em MSISDN, inclusive quando passam por workflows.
Cobertura funcional inclui:
- `consultar_faturas`
- `consultar_plano`
- `invoice_explanation`
- `consultar_vas`
- `consultar_historico_vas`
- `cancelar_vas_avulso`
- `tratar_vas_estrategico`
- `validar_vas_subject`
- `validar_contestacao`
- `contestar_cobranca`
- `pro_rata`
- `termino_desconto`
- `valor_divergente`
- `consultar_status_solicitacao`
- `enviar_sms`
- `recuperar_fatura_pdf`
- `finalizar_atendimento`
`consultar_linhas_autorizadas` é a fonte de autorização da ALT2 e não passa pela própria política para evitar dependência circular.
`buscar_informacao` não depende de linha e `retomar_workflow` apenas retoma execução já iniciada.
## 10. Arquivos alterados/criados
### Política de linha
```text
contas_mcp/servers/contas_mcp_server/line_policy.py
contas_mcp/servers/contas_mcp_server/line_policy_alt1.py
contas_mcp/servers/contas_mcp_server/line_policy_alt2.py
```
### Novo serviço MCP de autorização
```text
contas_mcp/servers/contas_mcp_server/authorized_lines_service.py
app/domain/contas/fixtures/authorized_lines.json
contas_mcp/servers/contas_mcp_server/main.py
```
### Referência conversacional e seleção da política
```text
app/domain/contas/line_reference.py
scripts/select_line_policy.py
```
### Testes e documentação
```text
tests/migration/test_line_policy_alternatives.py
docs/RELATORIO_POLITICAS_DE_LINHA_ALT1_ALT2.md
```
## 11. Validação
Testes específicos da política e do novo mock:
```text
12 passed
```
Smoke ALT2:
```text
policy = authorized_related_lines
authorized = [11999999999, 11988884321]
requested = final 4321
allowed = true
effective = 11988884321
```
Após o smoke, ALT1 foi restaurado e validado como política ativa entregue.
Suíte completa de migração com ALT1 ativa:
```text
765 passed
```
## 12. Decisão arquitetural
Responsabilidades finais:
| Camada | Responsabilidade |
|---|---|
| Framework | identidade/contexto, execução genérica, terminalidade e short-circuit de tools |
| Agent/MCP Contas | política ALT1/ALT2 e integração de autorização |
| `consultar_linhas_autorizadas` | informar quais linhas a identidade autenticada está autorizada a operar |
| Backend real futuro | fonte de verdade de titular/dependentes/autorização |
| LLM | interpretar a referência conversacional; nunca conceder autorização |
A principal regra arquitetural é:
> **linha mencionada ≠ linha autorizada**
A autorização precisa vir de uma fonte explícita e confiável. Na versão demonstrativa essa fonte é o mock MCP `consultar_linhas_autorizadas`; em produção, deve ser substituída pela integração corporativa correspondente.

View File

@@ -1,295 +0,0 @@
# Validação da reconstrução
## Resultado
- Sintaxe Python (`compileall`): **PASS**
- YAML de `config/`: **PASS**
- Testes de domínio mock: **4 PASS**
- Smoke MCP: **PASS** para faturas, invoice explanation, VAS, histórico, cancelamento e contestação
- Diretório do pacote anterior presente: **NÃO**
- Imports do namespace anterior em `app/`, `mcp/`, `config/`: **0**
- `.env`: **preservado byte a byte**
## Limitação do ambiente de construção
O runtime usado para montar o pacote não possui `langgraph` instalado globalmente. Por isso o teste de import/execução do `StateGraph` completo não foi executado aqui. O projeto declara `langgraph` em `pyproject.toml`; rode `uv sync` antes de subir o backend.
## Aceite recomendado em ambiente do projeto
```bash
uv sync
pytest -q tests/migration
uv run uvicorn contas_mcp.servers.contas_mcp_server.main:app --port 8400
uv run uvicorn app.main:app --port 8000
```
Depois execute os cenários descritos em `MANUAL_AGENT_CONTAS_MIGRADO.md`.
## Atualização de paridade - 2026-08-18
Gate local atual: **338 passed / 3 skipped** em `tests/migration`.
Os skips dependem de bibliotecas não instaladas no runtime de construção (principalmente LangGraph/jellyfish) e permanecem habilitados para execução após `uv sync`.
Contratos adicionais corrigidos nesta rodada:
- VAS History: `GET ?msisdn=...`, `clientId`, `messageId` e `Authorization`.
- Contract Information: `clientId` (não `client_id`) e Basic Auth.
- Profile/Line Info: header `ClientID` conforme contrato original.
- Cancelamento VAS: `messageId` sempre não vazio, além de `channel=AIAGENTCR` e `interactionProtocol`.
Gates estruturais mantidos:
- zero imports de `agente_contas_tim` em `app/`, `mcp/` e no código-fonte do framework;
- zero imports diretos de `langgraph.graph` no domínio Contas;
- workflows do domínio executados por `agent_framework.workflows.WorkflowRuntime`;
- `FrameworkStateGraph` usado para composição do grafo principal;
- `.env` preservado como arquivo principal de configuração.
## Atualização de paridade - continuação 2026-08-18
Gate local atual: **400 passed / 2 skipped** em `tests/migration`.
Skips restantes:
- `test_original_item_matcher_transcription.py`: requer `jellyfish`, dependência declarada no projeto e instalada por `uv sync`.
- `test_original_workflow_cases.py`: requer `langgraph`, dependência declarada no projeto e instalada por `uv sync`.
Novas coberturas e correções comprovadas nesta rodada:
- cenário real `cy0001` voltou a integrar a regressão de `vas_variation`; o skip causado por caminho incorreto do harness foi removido;
- replay pós-finalização preserva `terminal_status` e possui fallback seguro quando a sessão é restaurada sem a última fala;
- transformação de transcrição `Fim/Mim -> Sim` foi validada contra a matriz original de fronteira de fala inteira;
- `processing_interruption` interrompível voltou a usar classificador LLM leve do **framework**; sem classificador/erro/resultado negativo o comportamento é replay fail-safe;
- os dois templates oficiais do framework receberam o mesmo fluxo de classificação de interrupção;
- SMS recuperou o default canônico `senderName=TIM Brasil`;
- Service Request Status prioriza `messageId`/`ura_call_id` antes de `session_id`;
- o compositor determinístico de mensagem de cancelamento VAS foi portado e os 19 testes originais passam;
- `cancelar_vas_avulso` encadeia `cancelamento_vas_avulso -> contestacao_tool` usando **dois WorkflowRuntime do framework**;
- o MCP inicializa WorkflowRuntime/checkpointer/idempotência de forma lazy, evitando abrir Oracle durante import/health/tools-list;
- o `IdempotencyStore` selecionado pelo framework é agora realmente injetado nos actions do Contas, eliminando o fallback local involuntário para memória;
- cancelamento usa VAS History como fallback e bloqueia recancelamento quando `canCancel=false`;
- resultado em lote expõe `cancelados`, `nao_encontrados`, `nao_cancelados` e `itens_para_contestacao`, preservando sucesso parcial;
- finalização normaliza status/aliases e summary conforme o original;
- finalização informacional cria protocolo fechado somente quando necessário e evita duplicidade quando já existe protocolo;
- combinações canônicas de notas `VAS Bundle`, `VAS Estratégico` e `VAS Avulso` foram portadas e testadas.
### Gates estruturais desta versão
```text
agente_contas_tim em app/ 0
agente_contas_tim em mcp/ 0
agente_contas_tim em agent_framework/src 0
langgraph.graph em app/ 0
langgraph.graph em mcp/ 0
```
O LangGraph continua interno ao `agent_framework_oci` por `FrameworkStateGraph` e `WorkflowRuntime`.
## Atualização de paridade - continuação 2026-08-18 (baseline 420)
Gate local atual: **420 passed / 2 skipped** em `tests/migration`.
Novas correções comprovadas desde a baseline 400:
- plano família mantém o MSISDN do titular na contestação e o MSISDN real do dependente no cancelamento;
- `invoice_detail` corrige deterministicamente a linha do item antes do side effect;
- `CVAL` encerra `contestacao_tool` imediatamente quando bloqueia a operação, sem seguir para SMS/contrato/SR;
- o MCP propaga `success=false`, mensagem e estado sistêmico quando a contestação é bloqueada;
- finalização de `invoice_explanation` cria protocolo informacional apenas quando o workflow realmente executou, não por mero prefetch;
- protocolo VEB já fechado é reutilizado sem nova abertura/fechamento; `force_rt15_finalization_protocol` força um RT-15 novo quando solicitado;
- `suppress_cvn_protocol_ic` preserva a semântica de VAS estratégico deferido;
- `WorkflowRuntime` preserva snapshot parcial, nodes e trace quando uma action posterior falha;
- Billing Analysis agora carrega metadata de tentativas e gera RCT.079-084 por tentativa;
- corrigido bug de `msisdn` duplicado em `preparar_invoice_explanation`;
- corrigido bug de inicialização de `AgentWorkflow`: router/agentes/grafo estavam em código inalcançável após `return`;
- `InvoiceContextService` foi reconstruído sobre `agent_framework.cache.Cache`, com isolamento por sessão, TTL, prefetch e reaproveitamento de CompleteInvoices/Billing Analysis/detalhe.
Os dois skips continuam sendo exclusivamente dependências deste runtime de construção:
- `jellyfish` para regressão fonética completa;
- `langgraph` para os 18 casos reais de WorkflowRuntime.
## Baseline 435 testes — continuação de paridade
Nesta baseline foram adicionadas as seguintes garantias:
- `InvoiceContextService` com single-flight por sessão/fatura, cache incompleto sensível a `include_detail`, CVN.002/CVN.006/CVN.007 como `business_events` deduplicados por sessão e sem republicação em cache hit.
- Metadados de prefetch: `fetch_elapsed_ms`, `task_timings`, `cache_age_ms` e erros por subconsulta.
- Latch genérico `business_workflows_executed` no `AgentRuntimeMixin`; workflows em `PAUSED` já contam como executados e o latch é persistido no patch transacional.
- Cancelamento em lote com concorrência máxima 5 (configurável por `TIM_CANCELAMENTO_BATCH_CONCURRENCY`) e protocolo por linha antes do side effect; falha de protocolo bloqueia o cancelamento daquela linha.
- `WorkflowRunResult.error_details` preserva fatos estruturados de exceções externas sem acoplamento do framework a TIM.
- Contestação FAILED mapeia mensagem do provider/protocolo parcial quando disponíveis e diferencia erro de negócio de falha sistêmica.
Resultado local: **435 passed / 2 skipped** em `tests/migration`.
Os dois skips continuam dependentes de `langgraph`/`jellyfish` indisponíveis neste runtime de construção.
## Baseline 440 testes - continuação
- 440 testes de migração passando; 2 skipped por dependências indisponíveis neste runtime (`langgraph`/`jellyfish`).
- Cancelamento VAS: falha de bloqueio/cancelamento pode permanecer candidata à contestação sem mascarar sucesso.
- Resposta composta preserva protocolos de cancelamento por linha + protocolo de contestação e sinaliza finalização sugerida.
- Plano família: protocolo de cancelamento em dependente resolve `socialSecNo` via LineInfo da própria linha.
- Registro de protocolo aceita aliases de resposta `interactionProtocol`, `protocolNumber`, `protocol` e `protocolo`.
- Finalização informacional recalcula Bundle/Estratégico/Avulso pela evidência de `invoice_detail`/Billing Analysis quando disponível.
## Baseline 533 - dependências de regressão e finalização
- `tests/migration`: **533 passed / 4 xfailed / 0 skipped**.
- Os quatro `xfail` são limitações históricas documentadas do ranking fonético (`gueimiloft`, `tim miusic`, `apou`, `agebeo max`), não testes ignorados por dependência.
- `jellyfish` deixou de ser requisito obrigatório: o domínio possui fallback puro-Python para Jaro-Winkler, Levenshtein e chave fonética. A biblioteca externa pode ser usada como aceleração, mas o projeto e a regressão não dependem dela.
- Os 18 casos históricos de workflow não usam mais `importorskip(langgraph)`. Em builders offline executam pelo backend determinístico **explicitamente opt-in** do `WorkflowRuntime`; em produção o backend padrão continua sendo LangGraph e a ausência de `langgraph` continua sendo erro de configuração.
- Finalização validada adicionalmente para: CVN.008/CVN.009, MPI.006/MPI.005, RCT.085/RCT.086, CVN.010/CVN.011, SAD.001 e árvore SAD opcional; protocolo informacional canônico de invoice explanation; supressão em handoff; ausência de aceite informacional após workflows transacionais; classificação de VAS estratégico/avulso sem invoice detail.
### Lockfile
O `uv.lock` herdado de snapshots anteriores foi removido porque ainda descrevia o pacote legado (`agente-contas-tim`) e dependências que já não pertencem ao projeto (`jellyfish` obrigatório, NeMoGuardrails/LangChain extras, entre outras). O primeiro `uv sync` deve regenerar o lock a partir do `pyproject.toml` atual.
## Baseline 550 - finalização e matcher sem skips/xfails
Validação consolidada desta etapa:
```text
550 passed
0 skipped
0 xfailed
```
Coberturas adicionadas nesta etapa:
- finalização conversacional diferencia encerramento genérico de contexto real de fatura;
- prefetch/invoice context pode garantir CVN.002/CVN.006 e RT-15 conforme semântica histórica;
- `conversation_unresolved_transition_emitted` impede reemissão indevida de CVN/MPI positivos;
- VEB terminal com protocolo fechado não cria RT-15 duplicado nem reemite CVN/MPI;
- evidência da fatura prevalece sobre tipo informacional salvo em match total;
- match parcial mescla inferência da fatura com tipo salvo ainda não representado;
- sem match na fatura, tipos salvos conflitantes são descartados;
- protocolo já existente impede RT-15 duplicado;
- alias `cpf` é propagado como `socialSecNo` no protocolo informacional;
- matcher de transcrição resolveu os quatro casos históricos antes marcados como xfail.
### Matcher sem dívida conhecida no catálogo de regressão
O `SimilarityItemMatcher` passou a combinar:
- similaridade de frase;
- similaridade fonética;
- alinhamento token-a-token;
- dupla evidência grafia + fonética por token.
Isso corrigiu explicitamente:
- `gueimiloft` -> `Gameloft`;
- `tim miusic` -> `TIM Music`;
- `apou` -> `Apple Music`;
- `agebeo max` -> `HBO Max`.
## Baseline 556 — paridade VAA (2026-08-18)
A regressão de migração passou a executar 556 testes sem skip/xfail.
Nesta etapa os testes históricos do projeto original foram usados diretamente como catálogo para restaurar a família VAA sem trazer o publisher legado:
- cancelamento VAS: VAA.001/VAA.002/VAA.003 no caminho feliz e VAA.004 em falha operacional;
- contestação: VAA.005 + VAA.006/VAA.007 e VAA.008/VAA.009 conforme sucesso e elegibilidade do código de barras;
- SMS: VAA.012/VAA.014 em sucesso e VAA.013/VAA.015 em falha, mantendo o workflow ativo;
- atualização/fechamento de SR: VAA.016/VAA.017.
Os events são retornados como `business_events`; transporte, sequence e fan-out permanecem responsabilidade exclusiva do `AgentObserver`/analytics do agent_framework_oci.
## Baseline 560 testes - metadata corporativa TIM
- 560 testes de migração passando, sem skips/xfails.
- Business events preservam contexto corporativo: `agentProtocolId`, `adjustedProtocol`, `billingId`, `uraCallId`, `channelId`, `sessionId`, `messageId` e `agentSpecificData`.
- Contestação VAA.005-009 preserva itens/valores ajustados e protocolo.
- Finalização CVN.010/011 preserva protocolo TIM/URA e billing context.
- RCTs derivados de retry HTTP carregam `apiUrl`, `apiStatusCode`, `apiResponsePayload` e `latencyMs`.
- SMS e Service Request Status carregam metadata de transporte e contexto de protocolo.
- `tim_payload_mapper` aceita o formato histórico `agentSpecificData` JSON-string e o converte para o objeto canônico TIM no payload final.
## Baseline 565 — metadata MPI/VEB/SAD e VAS estratégico
- Regressão: **565 passed / 0 skipped / 0 xfailed**.
- `VEB.*`, `MPI.*`, `CVN.*` e `SAD.*` passam a carregar metadata conversacional TIM via `_event_context`: `customerMessage`, `llmResponse`, `messageId`, `sessionId`, `channelId`, `uraCallId`, `billingId` e `sessionEndAt` quando aplicável.
- `SAD.001` normaliza `session_end_at` ISO-8601 para epoch milliseconds.
- VAS Estratégico recuperou a semântica histórica: `NAO` estratégico -> `VEB.004 -> VEB.006 -> VEB.007`; Bundle + `NAO` -> `VEB.004 -> VEB.005 -> VEB.007` e protocolo deferido para finalização; `SIM` -> `VEB.003` e registro de atendimento.
- Invoice Explanation (`MPI.005/006`), Pró-Rata (`MPI.010`) e cancelamento (`MPI.011`) usam o mesmo enriquecimento de contexto.
## Baseline 570 testes
- `pytest -q`: **570 passed**, 0 skipped, 0 xfailed.
- `pyproject.toml` limita o gate padrão a `tests/`, evitando coleta acidental dos scripts de teste duplicados existentes dentro dos templates do framework.
- `cancelar_vas_avulso` redireciona automaticamente para `vas_estrategico` quando o `InvoiceResolver` classifica a cobrança como estratégico/bundle.
- `invoice_explanation` recuperou `recomenda_finalizacao/status_finalizacao_sugerido` por branch do `WorkflowRuntime`.
- `pro_rata` recuperou recomendação de finalização nos branches `registrar_aceitou` e `registrar_nao_controle`.
- VAS Estratégico recuperou a busca de orientação do parceiro sem RAG próprio: a action declara `requires_rag/rag_queries`, e o `AgentRuntimeMixin` executa `RagService` do framework.
## Baseline 578 - wrapper cancelamento + LLM composition
- 593 testes passando; zero skipped/xfail.
- Cancelamento preserva `auto_finalize_on_failure` em falha técnica de contestação.
- Item já contestado não mascara cancelamento concluído como falha sistêmica.
- `cancelamento_vas_protocol` e todos os protocolos de resposta são preservados.
- Plano família contesta titular + dependentes em uma única `contestacao_tool`.
- Lote com muitos no-match continua contestando exatamente os candidatos elegíveis.
- Pró-rata usa `requires_llm_composition` do framework em vez de gateway LLM de domínio.
## Continuação — paridade explícita dos wrappers (baseline 593)
- `contestacao_tool` volta a garantir `tipo_atendimento=contestacao` e preserva o contexto do turno (`message_id`, `customer_message`, `ura_call_id`, `channel_id`, `ani`, `invoice_id`).
- Falhas de contestação são diferenciadas entre erro técnico (`erro_falha_sistema`) e conflito/item já contestado com mensagem/protocolo do provider.
- Cancelamento composto aceita tanto `contestation_candidates` quanto o alias histórico `itens_para_contestacao`.
- Resultado agregado em `cancelados/nao_cancelados` é aceito mesmo quando `results[]` não repete a flag `success`.
- `cpf` volta a ser alias de `social_sec_no` e é normalizado para dígitos antes de protocolo/contestação.
- Em fluxo misto Bundle + Estratégico no branch NÃO, o protocolo segue deferido para finalização, porém as `rag_queries` dos serviços estratégicos são preservadas.
- Falha parcial com candidato continua encadeando `cancelamento_vas_avulso -> contestacao_tool`; falha de SMS não transforma o cancelamento em falha.
- `pytest -q`: **593 passed, 0 skipped, 0 xfailed**.
## Baseline 599 testes — paridade explícita de wrappers
Validação consolidada: `599 passed`, `0 skipped`, `0 xfailed`.
A rodada adicionou regressões 1:1 baseadas nos wrappers históricos para:
- `itens_ja_contestados` preservados desde a action de contestação até o compositor de resposta;
- classificação de `contested_items`, `not_contested_items` e itens já contestados feita no domínio, não reconstruída no MCP;
- totais de contestação calculados somente sobre itens efetivamente aceitos;
- valor `0`/`0,00` retornado pela contestação tratado como ausência de valor útil para composição, usando o total cancelado;
- valores monetários normalizados em pt-BR na borda do MCP (`14,99`);
- `next_subject` não interfere na composição determinística de cancelamento;
- falha de serviço em `invoice_explanation` preserva a fraseologia canônica e não ativa auto-finalização.
Gates: `compileall` PASS, zero imports do namespace legado e zero imports diretos de LangGraph em `app/`/`mcp/`.
## Baseline 615 — contratos 1:1 dos wrappers históricos
A regressão foi ampliada para 615 testes executados, sem skips e sem xfails.
Nesta etapa foram portados como contratos explícitos do MCP novo os seguintes
outcomes do `backend_cancelar_vas_single` e `finalize_support` históricos:
- sucesso implícito quando o workflow reporta `cancelados[]` sem `success=true`;
- lista explícita vazia de candidatos não cria fallback indevido para contestação;
- no-match não chama contestação nem vocaliza valor como se houvesse ajuste;
- `protocol_closed` da contestação é preservado;
- flags internas `sms_sent` não vazam no contrato externo e falha de SMS é propagada por `sms_not_send_error`;
- cancelamento com protocolo e sem item efetivamente contestado continua resolvido;
- candidato explícito pode seguir para RT-02 mesmo após falha/no-match em RT-01;
- falha parcial de bloqueio/cancelamento continua elegível à contestação quando marcada pelo domínio;
- `holder_msisdn` não pode ser sobrescrito por linha dependente;
- item do titular não é marcado como dependente;
- titular + múltiplos dependentes são agregados em uma única contestação do titular;
- VAS estratégico após invoice explanation prioriza nota estratégica na finalização;
- Bundle + Estratégico deferidos geram RT-15 combinado na finalização;
- invoice explanation não abre protocolo informacional antes da finalização.
Gate executado:
```text
pytest -q: 615 passed
compileall app/mcp/framework: PASS
agente_contas_tim em app/mcp/framework runtime: 0
import direto langgraph.graph em app/mcp: 0
```

View File

@@ -108,3 +108,14 @@ def test_prompt_aoferta_confirmacao_nao_exige_repetir_justificativa():
)
assert "A confirmacao NAO precisa repetir a justificativa" in prompt
assert "o pedido transacional anterior basta" in prompt
def test_prompt_aoferta_trata_canal_alternativo_como_continuidade_do_mesmo_pedido():
prompt = build_aoferta_prompt(
"Não consegui concluir por aqui; siga pelo canal oficial para finalizar.",
"\nHistorico da conversa:\n[user] Quero cancelar este serviço\n",
)
assert "CONTINUIDADE POR CANAL ALTERNATIVO" in prompt
assert "ESSA MESMA acao" in prompt
assert "nao cria uma nova" in prompt
assert "outro alvo" in prompt

View File

@@ -600,3 +600,70 @@ async def test_cancelamento_redireciona_item_estrategico_para_workflow_correto(m
})
assert result["metadata"]["domain_redirect_from"] == "cancelar_vas_avulso"
assert result["metadata"]["domain_redirect_to"] == "tratar_vas_estrategico"
@pytest.mark.asyncio
async def test_cancelamento_usa_valor_vas_validado_quando_usuario_nao_informa_valor(monkeypatch):
import contas_mcp.servers.contas_mcp_server.main as main
class Runtime(FakeRuntime):
async def arun(self, name, payload, execution_id=None):
self.calls.append((name, payload, execution_id))
if name == "cancelamento_vas_avulso":
return {
"execution_id": "cancel-valor", "workflow_name": name, "workflow_version": 1, "status": "COMPLETED",
"output": {"cancelar_vas_avulso": {
"success": True,
"results": [{"success": True, "msisdn": "5511999999999", "subject": "Tamboro Mensal",
"service": {"name": "Tamboro Mensal", "details": {"valor": "14,99"}}}],
"contestation_candidates": [{"success": True, "msisdn": "5511999999999", "subject": "Tamboro Mensal",
"service": {"name": "Tamboro Mensal", "details": {"valor": "14,99"}}}],
}}, "state": {}, "trace": [],
}
return {
"execution_id": "cont-valor", "workflow_name": name, "workflow_version": 2, "status": "COMPLETED",
"output": {"registrar_protocolo": {"protocolo_id": "PRT-V"}, "abrir_contestacao_cliente": {"success": True, "items_response": []}},
"state": {}, "trace": [],
}
runtime = Runtime()
monkeypatch.setattr(main, "get_workflow_runtime", lambda: runtime)
monkeypatch.setattr(main, "service", FakeService())
await main._run_cancelamento_com_contestacao({"msisdn": "5511999999999", "subject": "Tamboro Mensal"})
item = runtime.calls[1][1]["items"][0]
assert item["claimedAmount"] == "14,99"
assert item["validatedAmount"] == "14,99"
@pytest.mark.asyncio
async def test_cancelamento_preserva_valor_informado_e_valor_vas_como_evidencias_distintas(monkeypatch):
import contas_mcp.servers.contas_mcp_server.main as main
class Runtime(FakeRuntime):
async def arun(self, name, payload, execution_id=None):
self.calls.append((name, payload, execution_id))
if name == "cancelamento_vas_avulso":
return {
"execution_id": "cancel-div", "workflow_name": name, "workflow_version": 1, "status": "COMPLETED",
"output": {"cancelar_vas_avulso": {
"success": True,
"results": [{"success": True, "msisdn": "5511999999999", "subject": "Tamboro Mensal",
"service": {"name": "Tamboro Mensal", "details": {"valor": "14,99"}}}],
"contestation_candidates": [{"success": True, "msisdn": "5511999999999", "subject": "Tamboro Mensal",
"service": {"name": "Tamboro Mensal", "details": {"valor": "14,99"}}}],
}}, "state": {}, "trace": [],
}
return {
"execution_id": "cont-div", "workflow_name": name, "workflow_version": 2, "status": "COMPLETED",
"output": {"registrar_protocolo": {"protocolo_id": "PRT-D"}, "abrir_contestacao_cliente": {"success": True, "items_response": []}},
"state": {}, "trace": [],
}
runtime = Runtime()
monkeypatch.setattr(main, "get_workflow_runtime", lambda: runtime)
monkeypatch.setattr(main, "service", FakeService())
await main._run_cancelamento_com_contestacao({
"msisdn": "5511999999999", "subject": "Tamboro Mensal", "valor": "29,98",
})
item = runtime.calls[1][1]["items"][0]
assert item["claimedAmount"] == "29,98"
assert item["validatedAmount"] == "14,99"

Some files were not shown because too many files have changed in this diff Show More