feat(extension-points): conta do disparo de campanha atravessa broker, Temporal e chamadas ao CRM (CRM-632) - #125
Conversation
…hamadas ao CRM (CRM-632) Três pontos de extensão novos (contrato 1.3.0), todos no-op por padrão: - outbound_headers: mapa opaco mesclado em todo publish do broker e nas chamadas ao CRM (CrmClientService e CrmInboxDispatcher). As headers do próprio runtime vencem em caso de conflito. - inbound_message_context: envolve o processamento de toda mensagem do broker com as headers com que ela foi publicada. Aplicado num decorator do adapter no BrokerModule, então nenhum consumer muda. - temporal_interceptors: interceptors do client e do worker de campanha; os de workflow entram como caminho de módulo, porque rodam no sandbox. O publish do broker ganha headers opcionais; os contratos Zod seguem .strict() e intocados.
…632)
updateCampaignStatus resolvia o DataSource por token string
(app.get('DataSource')), que o Nest não encontra: o workflow de campanha
parava no passo 2 e a campanha nunca saía do rascunho. Passa a gravar pelo
TenantDbContext, como getCampaignData já lê.
updateExecutionProgress recebe tenantId opcional que o workflow nunca
passa. Sem ele, usa o manager que um interceptor de activity já tenha
ligado no CLS, em vez de pedir outro ao tenant_db_context.
Reviewer's GuideAdds contract 1.3.0 extension seams that transparently carry opaque context from campaign execution through Temporal, broker transports, and CRM calls, while preserving runtime-owned headers and no-op standalone behavior. It also fixes campaign status updates to use the tenant-aware database seam and permits activities to reuse an interceptor-bound manager, with focused broker and Temporal tests. Sequence diagram for campaign context crossing Temporal and the brokersequenceDiagram
participant Workflow as CampaignWorkflow
participant Broker as ContextPropagatingBroker
participant Adapter as KafkaOrRabbitAdapter
participant Consumer as CampaignConsumer
participant Context as ExtensionPointContext
Workflow->>Context: outbound_headers()
Context-->>Workflow: opaque headers
Workflow->>Broker: publish(topic, payload, headers)
Broker->>Adapter: publish(topic, payload, mergedHeaders)
Adapter-->>Broker: message with headers
Adapter->>Broker: deliver(message)
Broker->>Context: inbound_message_context(message.headers, work)
Context->>Consumer: process message
Consumer-->>Context: processing complete
Context-->>Broker: result
Sequence diagram for outbound context on CRM callssequenceDiagram
participant Activity as CampaignActivity
participant Client as CrmClientService
participant Inbox as CrmInboxDispatcher
participant CRM as CRM
participant Context as ExtensionPointContext
Activity->>Client: buildHeaders()
Client->>Context: outbound_headers()
Context-->>Client: opaque headers
Client->>CRM: HTTP request with merged headers
Activity->>Inbox: dispatch()
Inbox->>Context: outbound_headers()
Context-->>Inbox: opaque headers
Inbox->>CRM: POST conversation with merged headers
Note over Client,CRM: Runtime-owned headers take precedence
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've reviewed your changes and they look great!
Sourcery assessment
Needs a human reviewer. This changes the live broker, Temporal worker/client, and CRM request paths, allowing headers and interceptors to affect messages and external calls. The defaults are no-ops and a revert removes the behavior going forward, but messages published or CRM requests made with incorrect propagated context cannot be undone by reverting.
…uard e lane de CI (CRM-632) - getHeaders (transporte do executeRequest, usado pelos nodes de jornada) passa a mesclar outbound_headers, como o buildHeaders; o documento promete "every call to the CRM". - updateCampaignStatus grava por runActivityInTenantDbContext, como o updateExecutionProgress: sem manager ligado, o overlay recusa em vez de fazer UPDATE de zero linhas no pool global. - campaign-execution.activities.ts sai da allowlist do guard de tenant-db-context (teto 23 -> 22): o arquivo não acessa mais o pool. - Lane context-propagation roda os specs do transporte e das activities, que nenhuma lane cobria. - EXTENSION_POINTS.md: inbound_message_context não deve segurar transação durante o work; o exemplo registra quatro dos pontos, não "todos".
Summary
1.3.0), todos no-op por padrão, para um contexto opaco atravessar os trechos do disparo de campanha que rodam fora de request:outbound_headers: mapa mesclado em todo publish do broker e nas chamadas ao CRM (CrmClientService.buildHeadersegetHeaders, que é o transporte legado dos nodes de jornada, eCrmInboxDispatcher.getHeaders). As headers do próprio runtime vencem em conflito.inbound_message_context: envolve o processamento de toda mensagem do broker com as headers com que ela foi publicada. Ligado num decorator do adapter (ContextPropagatingBroker, noBrokerModule), então nenhum consumer muda: packer, sender e tracker ganham o contexto de graça.temporal_interceptors: interceptors do client que inicia o workflow de campanha e do worker de campanha. Os de workflow entram como caminho de módulo (workflowModules), porque rodam no sandbox.IMessageBroker.publishganhaheaders?opcional; os dois adapters repassam e mantêm as próprias chaves (correlationId,messageId). Os contratos Zod decampaigns.pack/campaigns.sendseguem.strict()e intocados.Correção que veio junto:
updateCampaignStatusresolvia o DataSource comapp.get('DataSource')(token string), que o Nest não encontra. Na develop o workflow de campanha para no passo 2 e a campanha nunca sai do rascunho (reproduzido no E2E abaixo). Passa a gravar peloTenantDbContext, comogetCampaignDatajá lê. OupdateCampaignStatusgrava porrunActivityInTenantDbContext, como oupdateExecutionProgress: sem conta ligada, o overlay recusa em vez de fazer UPDATE de zero linhas no pool global. Os dois usam o manager já ligado no CLS por um interceptor de activity, porque o workflow não passatenantId. O arquivo sai da allowlist do guard de tenant-db-context (teto 23 → 22); o guard volta a pegardataSource.getRepository('Campaign')ali.Contrato: o
EXTENSION_POINTS.mddiz queinbound_message_contextnão deve segurar transação durante owork(os consumers gravam linha a linha e dão ack dentro dele).Test plan
context-propagationroda os specs do transporte e das activities, que nenhuma lane cobria (--forceExitpor causa de um handle aberto que ocrm-client.service.specjá tinha).npx jestnos arquivos tocados: broker (decorator + módulo), extension points, activities de campanha,tenant-activity-context, crm-client, messaging-channels e o snapshot de paridade do disparo (129/129).npx tsc --noEmitlimpo; lint limpo nas linhas alteradas.Dockerfile.flow), Temporal, Kafka, e evo-flow e CRM rodando com papéis Postgres sem BYPASSRLS, numa cópia do banco de dev:execute→ workflow →campaigns.pack→ packer →campaigns.send→ sender → tracker. Toda chamada ao CRM levaX-Evo-Tenant-Id(lista de contatos, 5 contatos hidratados com 200,POST /api/v1/conversations); campanha terminaCOMPLETEDe a execução é fechada.getCampaignData(Campaign ... not foundsob RLS); nenhuma chamada chega ao CRM.updateCampaignStatus(correção acima).POST /api/v1/conversationsvoltou 422 nosource_iddo inbox de WhatsApp (formatocampaign_<id>_<ts>), igual na develop: fica registrado à parte.Ordem de merge
Este PR antes do overlay (evo-enterprise-licensing-nestjs): o
register()do overlay troca os três pontos novos, e um registry sem eles recusa o nome no boot.Related PRs
Summary by Sourcery
Enable opaque account context to follow campaign execution across brokers, Temporal, and CRM integrations while ensuring campaign status writes use the tenant database context.
New Features:
Bug Fixes:
Enhancements:
CI:
Documentation:
Tests:
Chores: