ajuste no guardraild COE

This commit is contained in:
T3782834
2026-08-31 21:11:57 -03:00
parent 1a31478bfe
commit 9ed4782f9d
139 changed files with 4473 additions and 120 deletions

View File

@@ -1,148 +1,78 @@
"""Prompt do rail COER (coerência do input do cliente).
"""Prompt do rail COER (coerência semântica do input do cliente).
Roda no INPUT, em paralelo com PINJ (mesmo pool), num 20b. Decide se a fala do
cliente é aproveitável. Saída BINÁRIA (`1` passa / `0` descarta) — o `reason` é
texto fixo; pedir motivo antes do dígito foi medido e não paga (+170 ms, empate).
O COER responde uma pergunta estreita: existe significado conversacional recuperável
na fala do cliente? Ele não decide intenção, completude de parâmetros, executabilidade,
escopo de negócio ou se uma solicitação deve ser aceita. Essas decisões pertencem ao
router, aos contratos transacionais, aos validadores e ao mecanismo de clarification.
Descarta SÓ por três motivos:
(a) incompreensível — transcrição quebrada, palavra solta, conversa paralela;
(b) negação ambígua — "não" colado num pedido de AÇÃO do atendente, sem a vírgula
que decidiria a leitura ("não quero cancelar" × "não, quero cancelar");
(c) idioma (2026-08-10) — frase INTEIRA em inglês é STT quebrado, não cliente
bilíngue: descarta mesmo se ela se entende ou responde à pergunta pendente.
Ressalva: passa quando o agente pediu o NOME do item — nome de serviço É em
inglês (`coer_ok_0023`). ⚠️ A regra só funciona no ENQUADRAMENTO, acima do
gate de histórico (dentro de (a): 0/9 nos casos de inglês; no topo: 9/9),
porque o gate concede 1 a quem responde e o catch-all a quem pede algo
legível. Travado em `tests/guardrails/test_coerencia.py`.
O resto passa e é tratado adiante (matcher, TOX, OOS, orquestrador): referência
vaga, nome deformado, xingamento, assunto fora de fatura, resposta curta. O
histórico entra no prompt porque é ele que resolve fala curta e negação sem vírgula.
Dois bugs de produção fechados, ambos com a mesma assinatura — o modelo reconhece
a fala e escapa por uma regra de allow antes de aplicar (b):
- 2026-08-07, "não" seco no degrau 2 da retenção: (b) disparava só por começar
com "não" e o modelo COMPLETAVA a elipse com a ação que o AGENTE ofereceu.
Conserto: (b) exige que a fala PEÇA algo, e o teste da subtração proíbe
completar com a oferta do agente (`coer_ok_0027`: 161/220 → 340/340);
- 2026-08-10, "não gostaria de falar com a atendente" (`coer_ambig_0014`, 2/9):
a causa é o VERBO, não o gate nem o histórico (sonda 2×2 — condicional +
histórico curto 2/10 × "não quero" + o histórico longo do trace 10/10).
Conserto: gate vale só para a fala que "SÓ responde a ela"; (b) diz que
entender o pedido não dispensa o teste; a glosa do 1º exemplo cobre o
condicional. Alvo → 7/9, suíte 176,0 → 180,7/189.
⚠️ Protocolo: decida por BATCH (3 amostras de `--repeat 3` da suíte inteira, banda
de ruído ±4). `--repeat` focado engana nos dois sentidos — a mesma variante deu
7/10 focado × 0/9 batch, e o prompt atual dá 7/9 batch × 3/9 focado.
Variantes medidas e REJEITADAS (não retentar sem motivo novo) — a suíte está numa
fronteira zero-soma, cada cláusula compra um caso e vende outro:
- "a recusa soar clara não fecha" → CONTRADIZ a exceção "a fala segue dizendo
qual leitura vale": mata `coer_ok_0003` (7/9 → 0-1/9) em 3 variantes;
- exceção no GATE ("fala com 'não' ainda passa por (b)") → mata `coer_ruido_0011`
(9/9 → 0/9): exceção explícita REFORÇA o gate para todo o resto;
- "gostaria" na lista de modais de (b) → 169,7/189;
- few-shot NÃO é mais alavanca (era em 2026-08-05, +3,4 p.p.): +3 exemplos = empate
exato por +132 tokens; só o do NOME em inglês = 189,7/201 (arrasta a regra (c));
tirar exemplos custa mais do que os tokens que ocupam — inclusive o "não quero
entender porque…", que o controle FOCADO media como "sem efeito" e em batch vale
`coer_ok_0010` inteiro (9/9 → 1/9).
Tamanho: 1289 → 1334 (2026-08-07) → **1451 tokens** (cl100k). Suíte: **191,7/201
(95,4%)**, 67 casos. Detalhe por caso e histórico: `tests/llm_tests/README.md`.
Remedido em 2026-08-12 ao desfazer o revert (41979c4d): 193,7/204 (95,0%), 68 casos
— o novo `coer_ruido_0022` ("um" respondendo "sanei sua dúvida?", STT que não pegou
o "sim" → golden 0, reperguntar) sai de 3/10 no prompt antigo para 9/9 em batch só
com o gate "SÓ responde a ela", sem mudança extra de prompt.
A classificação continua totalmente delegada ao LLM. Não há listas de frases,
regexes ou exceções de domínio para liberar/bloquear entradas específicas.
"""
from __future__ import annotations
def build_coer_prompt(text: str, context: str = "") -> str:
"""Monta o prompt do rail COER.
"""Monta o prompt semântico do rail COER.
Args:
text: fala do cliente a classificar.
context: bloco de histórico já formatado por
``prompts._context.format_context_block`` (para este rail a última
fala do agente é PRESERVADA — é a pergunta pendente).
context: histórico já formatado, incluindo a pergunta pendente do agente
quando disponível.
Returns:
Prompt cuja resposta esperada é um único caractere: ``1`` ou ``0``.
"""
return f"""Você filtra a fala do CLIENTE no atendimento de fatura do provedor. A fala vem de
transcrição de voz e pode chegar truncada ou trocada. O atendimento é em português:
frase inteira em INGLÊS é STT quebrado, não cliente bilíngue — responda 0 mesmo que
ela se entenda ou responda à pergunta do agente; só não vale quando o agente pediu o
NOME do item, que é em inglês.
return f"""Você é o guardrail de COERÊNCIA SEMÂNTICA da fala do CLIENTE em uma conversa.
PRIMEIRO olhe o histórico. Se o agente terminou com uma pergunta e a fala SÓ responde a ela
(sim/não, "ainda não", nome de serviço, valor, uma das opções oferecidas), responda 1
— mesmo curta, estranha ou com o nome deformado pelo STT. Se não há pergunta pendente,
julgue a fala sozinha pelos casos abaixo, sem dar desconto.
Sua única responsabilidade é decidir se a fala contém significado conversacional
recuperável o suficiente para que as próximas camadas do sistema possam trabalhar.
Responda 0 (descartar) SÓ nestes dois casos:
NÃO tente decidir aqui:
- qual é a intenção do cliente;
- se a intenção mudou em relação ao turno anterior;
- se uma transação deve continuar, ser abandonada ou encerrada;
- se faltam parâmetros para executar uma ação;
- se um valor, nome, data ou outro parâmetro é válido;
- se a solicitação pertence ao escopo do atendimento;
- se uma ação é permitida por regra de negócio;
- se a fala precisa de clarification ou desambiguação posterior.
(a) NÃO DÁ PARA ENTENDER — você não conseguiria dizer em uma frase, SEM INVENTAR, o
que o cliente quer, responde ou reclama: transcrição quebrada, frase cortada no
meio, palavra ou letra solta, frase que soa completa mas cujo pedido não faz
sentido, ou fala dirigida a OUTRA PESSOA (o cliente conversando com quem está do
lado, sem falar com o atendimento). Palavra do domínio (plano, fatura, valor,
cpf) dentro de frase sem sentido não salva a fala. Fala VAGA não é
incompreensível: se ela aponta para o que está na tela ("esse aí", "isso aqui",
"esse negócio", "os valores"), responda 1 — perguntar qual item é do fluxo.
E se a última fala do agente pediu um NOME de item/serviço, nenhuma fala curta
é incompreensível: ela é a tentativa de dizer o nome, por mais estranha que
soe → 1 (reconhecê-lo é da etapa seguinte, que tem a fatura).
Essas responsabilidades pertencem ao router, ao estado transacional, aos validadores
e ao mecanismo de clarification. Portanto, uma fala pode ser compreensível mesmo
sendo incompleta para execução, contendo negação, discordância, reclamação, múltiplas
intenções, informalidade, erro gramatical ou referência que precise ser resolvida pelo
contexto.
(b) NEGAÇÃO AMBÍGUA — a fala começa com "não" E PEDE ALGO depois; entender o que ela
pede não a salva, quem decide é o teste. Faça o teste: tire
esse "não" do início e olhe SÓ o que sobra na fala — nunca complete com a ação
que o agente ofereceu. Se não sobra pedido nenhum ("não", "não sanou"), é
resposta ao agente → 1, seja qual for a pergunta pendente. Se o que sobra é
pedido de ação do atendente (cancelar, tirar cobrança,
ajustar/diminuir a fatura, transferir para atendente, encerrar a conta,
parcelar), sobram duas leituras opostas — recusa ("não quero cancelar") ou
pedido ("não, quero cancelar") — e a vírgula que decidiria não veio na
transcrição: responda 0. Vale para qualquer verbo ("não quero/preciso/posso",
"não quero que vocês...", "não cancela").
Responda 1 se: vem vírgula, "porque" ou "mas" depois do "não"; há sujeito antes
do "não" ("eu não quero cancelar"); a fala segue dizendo qual leitura vale; ou o
que sobra sem o "não" não é ação do atendente (pagar, reconhecer, entender,
mudar de plano).
Use o histórico somente para interpretar elipses, respostas curtas e referências ao
turno anterior. Nunca complete a fala inventando uma intenção que não esteja apoiada
pela própria fala ou pelo contexto imediato.
Responda 1 em TODO o resto, inclusive:
- pedido, queixa, dúvida ou desabafo que você entende, mesmo com erro de transcrição,
gíria, xingamento, número solto ou assunto fora de fatura (outros filtros cuidam);
- nome de serviço estranho ou deformado, inclusive quando o agente pediu para repetir
o nome do serviço;
- pedido de tempo, "alô?", agradecimento, despedida.
Responda 1 quando for possível identificar, sem inventar, pelo menos um conteúdo
conversacional útil: uma intenção, pergunta, resposta, afirmação, negação, reclamação,
referência, escolha, valor, nome, pedido de esclarecimento, encerramento ou mudança de
assunto. Não exija que esse conteúdo já seja suficiente para executar uma ferramenta.
Dúvida se entendeu a fala → 1. Pergunta ou pedido claro dirigido ao atendimento, mesmo
fora do assunto de fatura → 1. Dúvida entre as duas leituras da negação → 0.
Responda 0 somente quando, mesmo considerando o contexto imediato, não houver
significado conversacional recuperável com segurança — por exemplo, transcrição
fragmentada, palavras desconexas, fala cortada antes de formar qualquer relação
semântica, ou conversa paralela sem solicitação dirigida ao atendimento.
Exemplos (ilustram a regra, não são lista de falas):
- "não quero parcelar a fatura" → 0 (sem a vírgula, pode ser "não, quero parcelar");
idem no condicional, "não gostaria de parcelar a fatura"
- "eu não quero parcelar a fatura" → 1 (o "eu" antes do "não" fecha a leitura)
- "não quero parcelar, quero só entender o valor" → 1 (a fala diz qual leitura vale)
- "não vou pagar essa multa" → 1 (pagar não é ação do atendente: a queixa é a mesma)
- "não", depois de "sanou sua dúvida?" → 1 (responde a pergunta pendente)
- "deixe zero", depois de "qual o nome do serviço?" → 1 (pode ser o nome que o STT
deformou — "Deezer"; reconhecer o nome é da etapa seguinte, que tem a fatura)
- "não quero entender porque a conta subiu tanto" → 1 (entender é dúvida, não ação)
- "olha o menino ali pegando o negócio lá" → 0 (não dá para dizer o que o cliente quer)
- "bota dois planos um em cima do outro pra cá" → 0 (soa ordem, não quer dizer nada)
- "está cobrando um" → 0 (cortada no meio: não dá para saber de quê)
Critério decisivo:
- compreensível mas incompleto/ambíguo para a regra de negócio -> 1;
- compreensível mas com possível mudança de intenção -> 1;
- compreensível mas sem todos os parâmetros -> 1;
- compreensível e contendo negação/discordância -> 1;
- impossível determinar qualquer conteúdo conversacional sem inventar -> 0.
Na dúvida entre "há significado, mas outra camada precisa esclarecer" e "não há
significado recuperável", escolha 1. O COER deve bloquear apenas incompreensibilidade
semântica real, não incerteza de negócio.
------------------------------------{context}
Fala do cliente:
{text}
------------------------------------
Responda APENAS um caractere: 1 (aproveitável) ou 0 (descartar).
Responda APENAS um caractere: 1 (semanticamente compreensível) ou 0
(semanticamente incompreensível).
"""

View File

@@ -0,0 +1,27 @@
# 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

@@ -0,0 +1,71 @@
# 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

@@ -0,0 +1,27 @@
# 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

@@ -0,0 +1,27 @@
# 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

@@ -0,0 +1,63 @@
# 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

@@ -0,0 +1,24 @@
# 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

@@ -0,0 +1,29 @@
# 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

@@ -0,0 +1,46 @@
# 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

@@ -0,0 +1,51 @@
# 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

@@ -0,0 +1,104 @@
# 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

@@ -0,0 +1,472 @@
# 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

@@ -0,0 +1,223 @@
# 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

@@ -0,0 +1,67 @@
# 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

@@ -0,0 +1,43 @@
# 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

@@ -0,0 +1,26 @@
# 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

@@ -0,0 +1,83 @@
# 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

@@ -0,0 +1,112 @@
# 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

@@ -0,0 +1,302 @@
# 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

@@ -0,0 +1,423 @@
# 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

@@ -0,0 +1,295 @@
# 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
```

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