Skip to content

feat(extension-points): conta do disparo de campanha atravessa broker, Temporal e chamadas ao CRM (CRM-632) - #125

Merged
gomessguii merged 3 commits into
developfrom
fix/CRM-632-conta-no-disparo-de-campanha
Sep 21, 2026
Merged

gomessguii merged 3 commits into
developfrom
fix/CRM-632-conta-no-disparo-de-campanha

Conversation

@daniloleonecarneiro

@daniloleonecarneiro daniloleonecarneiro commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Três pontos de extensão novos (contrato 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.buildHeaders e getHeaders, que é o transporte legado dos nodes de jornada, e CrmInboxDispatcher.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, no BrokerModule), 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.publish ganha headers? opcional; os dois adapters repassam e mantêm as próprias chaves (correlationId, messageId). Os contratos Zod de campaigns.pack/campaigns.send seguem .strict() e intocados.
  • Nenhum ponto novo carrega vocabulário de conta: o community só transporta um mapa que não lê. Quem põe e usa a conta é o overlay.

Correção que veio junto: updateCampaignStatus resolvia o DataSource com app.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 pelo TenantDbContext, como getCampaignData já lê. O updateCampaignStatus grava por runActivityInTenantDbContext, como o updateExecutionProgress: 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 passa tenantId. O arquivo sai da allowlist do guard de tenant-db-context (teto 23 → 22); o guard volta a pegar dataSource.getRepository('Campaign') ali.

Contrato: o EXTENSION_POINTS.md diz que inbound_message_context não deve segurar transação durante o work (os consumers gravam linha a linha e dão ack dentro dele).

Test plan

  • Lane nova context-propagation roda os specs do transporte e das activities, que nenhuma lane cobria (--forceExit por causa de um handle aberto que o crm-client.service.spec já tinha).
  • npx jest nos 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 --noEmit limpo; lint limpo nas linhas alteradas.
  • E2E com imagens reais (community desta branch + imagem enterprise do overlay por cima, via Dockerfile.flow), Temporal, Kafka, e evo-flow e CRM rodando com papéis Postgres sem BYPASSRLS, numa cópia do banco de dev:
    • branch + overlay: execute → workflow → campaigns.pack → packer → campaigns.send → sender → tracker. Toda chamada ao CRM leva X-Evo-Tenant-Id (lista de contatos, 5 contatos hidratados com 200, POST /api/v1/conversations); campanha termina COMPLETED e a execução é fechada.
    • develop + overlay: para no getCampaignData (Campaign ... not found sob RLS); nenhuma chamada chega ao CRM.
    • community puro desta branch, sem overlay: nenhuma header de conta sai; a campanha termina. Na develop, o mesmo cenário para no updateCampaignStatus (correção acima).
  • O POST /api/v1/conversations voltou 422 no source_id do inbox de WhatsApp (formato campaign_<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:

  • Add three no-op extension points for propagating opaque context through broker messages, CRM requests, and campaign Temporal clients and workers.
  • Propagate optional broker headers across Kafka and RabbitMQ while preserving transport-owned headers.

Bug Fixes:

  • Persist campaign status updates through the tenant database context, preventing campaigns from remaining in draft under tenant-aware database security.

Enhancements:

  • Wrap broker consumers with inbound message context without requiring consumer changes.
  • Apply extension-provided outbound headers to CRM client and inbox dispatcher requests.
  • Reuse the activity interceptor's tenant-bound entity manager when campaign activities update data without an explicit tenant identifier.

CI:

  • Add a dedicated context-propagation CI lane covering broker, CRM, Temporal activity, and tenant database context tests.

Documentation:

  • Document the three new extension points, their versioning, precedence, Temporal sandbox behavior, and transaction constraints.

Tests:

  • Expand broker, CRM, Temporal activity, and tenant-context coverage for context propagation and tenant-scoped campaign status updates.

Chores:

  • Update the extension-point contract to version 1.3.0 and reduce the tenant database context guard allowlist.

…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.
@sourcery-ai

sourcery-ai Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds 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 broker

sequenceDiagram
    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
Loading

Sequence diagram for outbound context on CRM calls

sequenceDiagram
    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
Loading

File-Level Changes

Change Details Files
Introduces three versioned, no-op-by-default extension points for propagating opaque account context across broker messages, CRM requests, and Temporal execution.
  • Adds public types, registry entries, defaults, reset behavior, and contract version 1.3.0.
  • Merges overlay-provided outbound headers into broker publishes and CRM client/inbox requests while preserving runtime headers on conflicts.
  • Adds inbound broker wrapping for both subscription APIs so message headers surround consumer processing.
  • Injects client and worker Temporal interceptors, passing workflow modules as sandbox-compatible paths and the application DataSource to the provider.
EXTENSION_POINTS.md
src/evo-extension-points/index.ts
src/evo-extension-points/registry.ts
src/evo-extension-points/version.ts
src/modules/campaigns/services/campaign-workflow.service.ts
src/modules/temporal/campaign-worker.service.ts
src/shared/broker/context-propagating.broker.ts
src/shared/broker/broker.module.ts
src/shared/broker/interfaces/message-broker.interface.ts
src/shared/broker/adapters/kafka-broker.adapter.ts
src/shared/broker/adapters/rabbitmq-broker.adapter.ts
src/shared/crm-client/crm-client.service.ts
src/shared/messaging-channels/dispatchers/crm-inbox.dispatcher.ts
Centralizes broker context propagation in a decorator without requiring consumer changes.
  • Extends IMessageBroker.publish with optional headers and forwards them through Kafka and RabbitMQ.
  • Keeps adapter-generated correlationId, messageId, and content-type authoritative.
  • Wraps subscribe and subscribePattern handlers while leaving acknowledgements inside the wrapped work path.
  • Adds unit coverage for default behavior, header precedence, and inbound context ordering.
src/shared/broker/context-propagating.broker.ts
src/shared/broker/context-propagating.broker.spec.ts
src/shared/broker/broker.module.ts
src/shared/broker/broker.module.spec.ts
src/shared/broker/interfaces/message-broker.interface.ts
src/shared/broker/adapters/kafka-broker.adapter.ts
src/shared/broker/adapters/rabbitmq-broker.adapter.ts
Fixes campaign status persistence and activity database-manager selection for RLS-aware execution.
  • Resolves the Campaign repository through TenantDbContext instead of the invalid string DataSource token.
  • Allows activities without a tenantId to use the EntityManager already bound in CLS by an activity interceptor.
  • Adds tests covering tenant-scoped status updates and reuse of the CLS-bound manager.
src/modules/temporal/activities/campaign-execution.activities.ts
src/modules/temporal/activities/campaign-execution.activities.spec.ts
src/modules/temporal/tenant-activity-context.ts
src/modules/temporal/tenant-activity-context.spec.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

…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".
@gomessguii
gomessguii merged commit ecc9ce3 into develop Sep 21, 2026
10 checks passed
@gomessguii
gomessguii deleted the fix/CRM-632-conta-no-disparo-de-campanha branch September 21, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants