Ajustes conforme relatorio de testes 2026-08-27

This commit is contained in:
2026-08-29 09:53:32 -03:00
parent 0ecff719b7
commit 88e1f070d7
791 changed files with 27040 additions and 29038 deletions

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,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

@@ -33,7 +33,7 @@ A suíte completa do projeto após as mudanças executa **715 testes com sucesso
| `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; mensagem específica sobre fim de fidelidade | Corrigida | Restaurada a semântica/mensagem original, incluindo plano, final da linha, fim do período promocional e retorno ao preço original. |
| `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. |
@@ -100,3 +100,13 @@ O novo arquivo `tests/migration/test_requested_tools_parity_pente_fino.py` verif
```
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.