Integrações e limites de responsabilidade¶
O core usa interfaces Python (Protocol) para depender de operações de negócio, enquanto os adapters traduzem essas operações para HTTP, SDK ou simulação. Uma factory é a função que escolhe e constrói a implementação com as configurações do ambiente. O ponto de composição principal é src/core/integrations/factory.py: build_dependencies() entrega um Dependencies para os casos de uso.
Esta referência descreve o código auditado em 14/09/2026, branch fix/ajustes, commit base 175a0bd986675a75ef760349b123595bf9fe714d. Implementação presente no repositório não comprova configuração, publicação ou funcionamento do serviço externo em produção.
Mapa de contratos¶
| Fronteira | Interface e operação | Implementação real | Simulação | Consumidor |
|---|---|---|---|---|
WhatsAppClient.send_message(to, text) |
WhatsAppCloudClient |
WhatsAppClientStub |
Webhook, orchestrator, painel e notificador | |
| OpenAI | LLMClient.chat(model, messages) |
OpenAIClient, Responses API |
Mocks injetados nos testes; sem stub selecionável por env | AgentInvokerImpl, reparo de formato, resumo |
| CRM | HubSpotClient.upsert_contact, create_deal, update_deal |
HubSpotHttpClient, base configurável |
HubSpotClientStub |
Confirmação cadastral e worker CRM |
| eGestor | EGestorClient.upsert_customer(payload) |
EGestorHttpClient |
EGestorClientStub |
execute_confirm_action() |
| Handoff | HandoffNotifier.notify(session) |
HandoffEmailNotifier ou HandoffNotifierWhatsApp |
HandoffNotifierStub |
Orquestração e renotificação agendada |
| Persistência | Repositories de sessão, mensagens, demandas, receipts, configuração, autenticação | Classes Dynamo* e CrmSyncRepository |
DynamoDB simulado por moto | Toda a aplicação; veja DynamoDB |
Os módulos de cada provider ficam em src/core/integrations/; LLMClient fica em openai_provider/client.py, e os demais contratos de provider em seus arquivos interface.py.
Respostas comuns¶
Apesar do nome histórico, StubResponse é usado também por implementações reais. O contrato em src/core/integrations/types.py é:
@dataclass(frozen=True)
class StubResponse:
success: bool
mock_id: str | None = None
detail: str | None = None
error_kind: Literal["transient", "permanent", "uncertain"] | None = None
mock_id contém o identificador real quando a chamada foi real. success=True nem sempre significa que existe ID: cada consumidor precisa exigir o ID quando necessário. CRM usa error_kind para separar falha temporária, bloqueio e resultado de criação desconhecido. WhatsApp/eGestor/handoff normalmente deixam esse campo vazio. A OpenAI retorna LLMCallResult e lança exceções próprias, em vez de usar este contrato.
Fluxo entre cadastro, CRM e ERP¶
sequenceDiagram
participant O as Orchestrator
participant C as ConfirmAction
participant CRM as API CRM
participant ERP as API eGestor
participant DB as DynamoDB
participant W as CrmSyncProcessor
O->>C: Coleta s=4 e dados confirmados
C->>C: Validar campos e gerar resumo
C->>CRM: upsert_contact
C->>ERP: upsert_customer
C-->>O: HANDOFF_PENDING ou api_failure
O->>DB: Sessão e intenção CRM na mesma transação
Note over O,DB: Notificação humana após persistência
W->>DB: Claim da projeção pendente
W->>CRM: upsert_contact e POST ou PATCH do deal
W->>DB: Confirmar revisão ou registrar falha
O worker é o único escritor de negócios/deals do fluxo da aplicação. Ainda existe upsert síncrono de contato CRM na confirmação, seguido do eGestor; não há transação distribuída nem rollback entre os providers. Se o contato falhar, o eGestor ainda é tentado. Ao substituir integrações, preserve a distinção entre sessão autoritativa no DynamoDB e projeção comercial eventual.
Configuração, segurança e diagnóstico¶
Os flags WHATSAPP_CLOUD_ENABLED, HUBSPOT_ENABLED, EGESTOR_ENABLED e HANDOFF_NOTIFIER_ENABLED selecionam adapters reais ou stubs. Desativar os três últimos não desativa a OpenAI: a factory cria OpenAIClient e load_settings() ainda exige chave. Os stubs retornam sucesso fictício, que permite avançar estados sem entregar nada externamente.
load_settings() é cacheado. Reinicie o processo após alterar env; loaders locais usam setdefault, portanto variáveis já presentes no processo prevalecem sobre .env. Valores diretos de OpenAI/CRM/eGestor prevalecem sobre SSM. Consulte a tabela completa de variáveis.
Logs passam por src/shared/logging.py, com JSON e scrubbing de chaves/expressões conhecidas. Isso não é anonimização universal: nomes de contato em firstname/lastname, chaves novas e exc_info não têm a mesma proteção que cadastral_data. Nunca adicione tokens aos logs nem copie payloads de clientes para exemplos. O e-mail operacional contém o telefone completo do cliente para uso da equipe; isso é diferente do log sanitizado.
Testar e substituir¶
tests/unit/integrations/test_factory.py verifica seleção, configuração incompleta e SSM; tests/unit/integrations/test_stubs.py verifica simulações. Os clients aceitam dependências de transporte (opener, sleeper, underlying ou ses_client), permitindo testar payload, erro e retry sem rede. Os smokes são mecanismos separados, capazes de produzir efeitos reais mediante flags.
Para uma migração, implemente o mesmo Protocol, mantenha os testes de contrato, substitua a construção na factory e transporte as configurações necessárias para settings, .env.example, SAM e deploy. O worker CRM tem outra construção de client em src/crm_sync/app.py; alterar apenas a factory principal não altera esse processo. Faça o mesmo para autenticação, payloads, IDs e classificação de erro: apenas mudar uma URL não torna APIs incompatíveis equivalentes.