Projeto do Agent Contas ORACLE

This commit is contained in:
2026-08-19 09:35:50 -03:00
commit 950a2bcd33
1366 changed files with 177217 additions and 0 deletions

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.

223
docs/MATRIZ_MIGRACAO.md Normal file
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.

295
docs/VALIDACAO_MIGRACAO.md Normal file
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
```