release: corte de agosto — develop para main (40 commits) - #123
Merged
Conversation
The Set Variable config UI offers numeric Increase/Decrease (preview shows +40), but the runtime never read `operation` — SetVariableNodeInput.nodeData didn't even declare it — so every op was a plain SET and "increase lead_score by 40" never accumulated (silent-success family, EVO-1740/EVO-1757). - Declare operation/value/category on nodeData (removes the `as any` casts). - Add loadSessionVariables() (mirrors conditional.node.ts / EVO-1913) to read the current value. - For increase/decrease: base = Number(prior) (unset/non-numeric prior -> 0), delta = Number(value); write base +/- delta as a number (so downstream numeric comparisons keep working). A non-numeric amount throws -> visible failure (success:false) instead of a silent no-op (AC #3). Plain SET and all other ops are unchanged. Scope: increase/decrease only. - New set-variable.node.spec.ts (jest): increase from numeric/unset/non-numeric prior, decrease, plain set unchanged + doesn't read session, default set, non-numeric amount fails visibly. 7 tests pass; tsc clean.
…ric error (EVO-2203)
The pipeline nodes already turn a CRM failure into a visible node error via
createErrorResult(error), but a 422 threw BadRequestException(body) whose
message is generic — so an archived-pipeline refusal reached the journey run
as "Bad Request Exception". The 422 handler now lifts the CRM envelope's
error.message to the exception message, so the run shows the reason
("Pipeline is archived and cannot receive conversations") while getResponse()
keeps error.code for callers that branch on it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…riting a wrong number
Code review follow-up on the increase/decrease fix. The arithmetic worked, but
every way it could NOT be honored still wrote a wrong value and reported
success — the same silent-success class the card set out to close (EVO-1740).
- Failed/missing session read no longer degrades to {}. Rebasing to 0 on an
unreadable prior silently turned lead_score 500 into 40 with success:true.
The read moves to BaseNode.readSessionVariables() (it was duplicated verbatim
here and in conditional.node.ts) and throws; conditional keeps its local
degrade-to-{} policy, set-variable lets it propagate.
- Empty/null amount no longer counts as 0. Number('') and Number(null) are 0,
so an empty Amount was a silent "increase by 0" — while the panel renders 1
as the placeholder in that state.
- A {{variable}} amount is resolved against the session before parsing. The
panel's Amount field has a variable picker and the executor passes nodeData
raw, so {{bonus}} arrived literal and aborted the whole journey.
- A non-numeric CURRENT value now fails instead of being clobbered to the delta
(AC#3); genuinely unset/null/'' still starts at 0.
- The array input shape honors operation too — it kept degrading increase to a
plain SET, the exact bug being fixed, on the other half of the contract.
- The error path reports a duration instead of Date.now() (an epoch timestamp
was flowing into logNodeExecution/trackNodeExecution as the node's duration).
Tests: 17 in set-variable.node.spec.ts (was 7), covering accumulation across
runs, {{var}} amounts, the array shape, and each visible-failure case.
tsc clean; conditional/base specs unaffected (47 pass).
… actually use (EVO-2203) Code review of #111. The 422 message lift landed in requestGeneric, whose only consumer is contacts-client. The three pipeline nodes call addToPipeline / moveToPipelineStage / createPipelineTask, which go through executeRequest — its 422 branch was untouched, so the journey run kept showing the raw JSON envelope ("CRM Validation error: {\"success\":false,\"error\":{...}}"). describeCrm422 now folds code and reason into that string. The "CRM Validation error" prefix stays: executeRequest matches on it to not retry a refusal. Spreading the envelope also overwrote a top-level `message` with the placeholder, so a 422 from render_record_invalid's fallback lost its reason ("Email is invalid" became "CRM rejected the request"). crmRejectionReason now prefers error.message, then a top-level message, then the raw body. Tests: the refusal is covered where the nodes read it (client level, both endpoints, no retry) and end to end on assign-to-pipeline with a real client — the node specs mock the CRM client away, which is why the gap went unseen.
…tive Comments only, no behavior change. Each one kept the non-obvious decision and dropped the before/after story, which belongs in the PR, not the source.
…riable-increment fix(EVO-1840): Set Variable node honors Increase/Decrease at runtime
feat(evo-flow): surface the CRM rejection reason on a 422, not a generic error (EVO-2203)
…ey/campaign node The EVO-1716 cutover removed the inbox-nested GET route (/api/v1/inboxes/:inbox_id/message_templates) and moved template listing to the flat /api/v1/message_templates?inbox_id=... endpoint. Frontend, controller, policy and the CRM's message_templates_service_token_spec were updated, but CrmClientService in evo-flow still called the removed nested route -> 404 -> resolveTemplate returned null -> a send-message node in messageMode: 'template' silently skipped the send. So every journey/campaign template message stopped being delivered. Fix: getInboxMessageTemplates now hits the flat endpoint with inbox_id as a query param. The existing resolveTemplate parsing (Array.isArray(raw?.data)) already matches the flat endpoint's success envelope, so nothing else changes. QA (jest + tsc): 3 suites / 40 tests green incl. a new crm-client.service.spec contract test that pins the flat URL (same pattern as the EVO-1272 moveToPipelineStage guard, which a mocked-client node spec cannot catch). Red counter-check: reverting to the nested URL fails that test (received the removed /inboxes/:id/... path). typecheck clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r (CRM-60) SegmentClickHouseQueryBuilderService interpolated segment definition values (customAttributeName, value, prop.path, labelId, templateId) directly into ClickHouse string literals, with no escaping. A value containing a single quote breaks out of the literal inside the INSERT INTO ... SELECT that computes segment membership. Route every user-controlled string through the existing (previously unused) SegmentQueryUtils.sanitizeStringValue helper before interpolation, and coerce numeric comparison operands through Number()/Number.isFinite instead of splicing raw text outside quotes.
… (CRM-60) The CustomAttribute node and the legacy UserProperty customAttributes[.<attr>] path each built their own ClickHouse SQL for the same delta-event read, and had already drifted: only the CustomAttribute branch accepted the legacy custom_attribute_changed event name and special-cased NotEquals/NotContains to include contacts with no event for the attribute (a contact who never set it trivially satisfies "not equal to X"). Extract both into a single buildCustomAttributeSubQuery, called by both entry points. While unifying, extend the same "include every contact, then flip on a positive match" handling to NotExists: it previously fell through to the generic per-event condition, so a contact who never triggered the attribute's change event got no row in the state table and was silently excluded from a "does not have this attribute" segment.
…s (CRM-60) Review follow-up on the escaping pass: timesOperator/times from the Performed node, the node id embedded in state_id, prototype-chain hits on the message event map, and LIKE wildcards in Contains values.
…60-consolidate-custom-attribute-query-builder fix(segments): consolidate custom attribute query builder, harden SQL
…contextual 404 error
…-template-flat-endpoint fix(CRM-209): call the flat message_templates endpoint from the journey/campaign node
…r e no cache de deletados (CRM-215) O CRM emite contact.label.added/removed (labelId em traits) e contact.deleted; o builder filtrava label_added/label_removed lendo properties e contact_deleted, então segmento por etiqueta computava 0 membros e contato excluído seguia contando. - Fonte única dos nomes (canônico + legado) e do subselect de deletados em queries/contact-event-names.ts, reusada pelo builder, pelo cache e pelo regex de reescrita da execução (que dependia do texto literal do CASE). - Remove queries/contact-exclusion-queries.ts (sem chamador). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RYhKfFRYqqdNnFTL7FSVpS
…ados estar quente (CRM-215) O recompute é incremental e o cache de deletados tem TTL de 5 min: se a única janela que continha o contact.deleted rodava com o cache vazio, o CASE virava WHEN 1=0 e o contato seguia no segmento até um recálculo completo. - Cache vazio mantém o subselect real (otimização nunca substitui correção). - A API de eventos sinaliza a ingestão de contact.deleted via EventEmitter (sem dependência de módulo Nest; só constantes compartilhadas) e o cache invalida o snapshot e ignora o cache por 15 s — a ingestão no ClickHouse é assíncrona e um fetch imediato re-cachearia um conjunto ainda sem a exclusão. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RYhKfFRYqqdNnFTL7FSVpS
… CASE (CRM-215) Achado de review na propria CRM-215. applyDeletedContactsOptimization casava `THEN '<qualquer>'` mas reescrevia sempre `THEN 'false'`. A maioria dos nos (LastPerformed, Performed sem contagem, WhatsApp/Web/SMS, UserProperty nao-argMax, e qualquer operador Exists) marca o contato deletado com o sentinela VAZIO, e a associacao e `argMaxMerge(last_value) != ''` -- reescrever para um literal nao-vazio devolvia o contato deletado ao segmento, justamente o AC que o card veio fechar. Regressao nova: o regex da develop (`[^)]*`) nao atravessava o `)` de `argMax(occurred_at, occurred_at)` e nunca casava, entao a reescrita era codigo morto. A validacao manual do card so exercitou nos Label, a unica familia com `validationInfo: Equals 'true'` e ramo ja `'false'`, portanto imune. - captura o literal do ramo e o reemite; replacer em funcao para que `$` dentro de um id nao seja lido como referencia de grupo - routing-config: adiciona `contact.deleted` canonico, que caia no fallback SYSTEM/low em vez de LIFECYCLE (mesmo defeito ja corrigido para o dotted de custom attribute) - remove o ramo morto de size === 0 no optimizeQueryWithDeletedContactsCache - testes: sentinela vazio preservado nos tres tipos de no afetados, `$` no id, e classificacao das duas grafias do evento de exclusao
…M-215)
Achado 5 do review. A migracao titulo→id do PR #318 so roda quando alguem abre
e salva o segmento no editor, e nao ha backfill: toda definicao gravada pelo
editor antigo guarda o TITULO da etiqueta, entao o AC1 valia so para segmento
novo ou reeditado.
O evento ja carrega os dois lados -- o handle_label_change do CRM monta
`traits = { labelName:, labelId:, source: }` -- entao aceitar as duas grafias na
camada de query faz a definicao antiga voltar a casar sozinha, sem migracao e
sem depender de reabrir o segmento. Mesma postura de "aceitar ambos" ja adotada
para os nomes de evento.
Backfill de verdade sairia caro e frágil: as definicoes moram no Postgres do
evo-flow, mas o mapa titulo→id mora no CRM -- a migration teria que chamar a API
do CRM.
… /g (CRM-215) Achados 9 e 10 do review, ambos no arquivo novo da propria PR. - sqlStringList dobrava aspas de ninguem: hoje so recebe constantes, mas e exportado e uma string com aspas quebraria o literal. - o regex do ramo CASE era uma constante exportada com flag /g, e .test() nela carrega lastIndex entre chamadas. Virou funcao que devolve instancia nova.
…al-contact-event-names fix(segments): casa os nomes canônicos de evento de contato e fecha a exclusão de deletados (CRM-215)
…(CRM-256) WebhookTrigger casava qualquer eventName comecando por `webhook.`. O pipeline de entregabilidade de e-mail grava cada callback de provedor em contact_events como `webhook.<platform>` e a materialized view events_to_journey_triggers_mv publica tudo no mesmo barramento, entao abertura/clique/bounce iniciava toda jornada com gatilho Webhook — com contact_id vazio, porque a linha de analytics nao resolve contato. - Match exato em `webhook.journey_trigger`, o nome que POST /api/v1/journeys/trigger/:journeyId emite; `eventName` na config do trigger sobrepoe o padrao. - Quando o evento traz `journeyId` nas properties, ele precisa ser o desta jornada — senao o webhook de uma jornada dispararia todas as outras. A MV segue sem WHERE de proposito: os outros sete tipos de gatilho leem do mesmo topico.
…-271) analyzeEventForJourneyTriggers seguia adiante com contactId vazio: consultava sessoes em espera pela chave '', casava gatilhos e chamava triggerJourneyExecution, que iniciava workflow Temporal com contactId '' e workflowId degenerado (journey-<id>-contact--<ts>). A guarda de reentrada e o claim de idempotencia tambem passavam a ser chaveados por contato vazio, entao todos os eventos sem contato compartilhavam a mesma chave. O barramento journey-triggers recebe toda linha de contact_events, e nem todo produtor resolve contato — as linhas de entregabilidade de e-mail gravam contact_id vazio. Descarte agora acontece antes de qualquer trabalho contact-scoped, com WARN nomeando evento e messageId em vez de sumir em silencio.
…a de config (CRM-256) Retorno de code review sobre o commit anterior. O ramo que comparava properties.journeyId com journey.id saiu. Ele nunca executava — nenhum produtor publica webhook.journey_trigger no barramento, ja que o endpoint manual chama startJourney direto — e quebrava um contrato que os outros sete handlers respeitam: ao avaliar condicoes de espera o processador chama matches() com um journey vazio, entao qualquer decisao baseada em journey.id tornava a espera insatisfazivel. Com isso somem tambem o JSON.parse que falhava aberto (payload malformado passava a casar toda jornada) e o log de erro em nivel debug. A resolucao do nome do evento passa a seguir a mesma ordem do EventTrigger (metadata, no, conditions), trata nome em branco como nao configurado e nao estoura com trigger nulo. Antes, um no configurado via conditions.eventName era ignorado em silencio — a mesma classe de bug, invertida. Os testes cobrem as duas fontes novas de config, o call site de condicao de espera e as assercoes de reason/metadata, que antes nao eram verificadas.
…spatch (CRM-271) Retorno de code review sobre o commit anterior. O descarte era logado em warn. Evento sem contato nao e anomalia: e a maior parte deste barramento — todo callback de entrega de e-mail e todo clique anonimo chegam assim. Em warn, o alerta de operacao dispararia continuamente. Passa a debug por evento, com o total corrido em info a cada mil descartes, para o volume continuar visivel sem virar ruido de alerta. O comentario anterior justificava o guard pelo dedup, que na verdade e chaveado por (jornada, contato, messageId) e portanto nunca colide entre eventos sem contato. O custo real e outro: getSessionsByContact carrega todas as sessoes em cache antes de filtrar, e uma sessao aberta sob contato vazio e devolvida a qualquer outro evento sem contato. O guard tambem passa a cobrir evento sem nome — nenhum dos oito handlers casa sem nome — e triggerJourneyExecution ganha a mesma checagem, em error, ja que chegar la sem contato significa que a barreira de entrada foi contornada. Os testes passam a exercitar processMessage, que e por onde o consumidor entra, e a construcao do processador virou fabrica compartilhada, sem a espera por setImmediate que cada bloco repetia.
…o (CRM-241)
Toda condição whereProperties de nó Performed / LastPerformed era montada como
JSONExtractString(properties, '<path>'), mas os eventos de contato que o CRM emite
chegam ao ClickHouse como `identify`: o payload inteiro vai em `traits` e a coluna
`properties` fica `{}` (EvoFlow::PayloadBuilder.build_identify não monta a chave
`properties`). O filtro nunca casava linha nenhuma, e falhava em SILÊNCIO nos dois
sentidos — o SQL era válido, rodava sem erro e simplesmente lia a coluna errada:
- Equals / Contains / Exists -> '' = 'VIP' sempre falso -> segmento vazio
- NotEquals / NotContains -> '' != 'VIP' sempre verdade -> o filtro NÃO
filtra e todo mundo entra. Numa campanha, é público ERRADO, não vazio.
A extração passa a escolher a COLUNA e extrair uma vez:
JSONExtractString(if(JSONHas(properties, 'p'), properties, traits), 'p')
`JSONHas`, e não `!= ''`, porque uma chave presente com valor vazio é resposta
legítima do produtor: cair para `traits` nesse caso trocaria um vazio deliberado
pelo valor de outra fonte e quebraria NotExists. Prefere `properties` e só cai para
`traits` quando a chave não existe, então evento `track` — que grava `properties` e
deixa `traits` em `{}` — produz SQL equivalente ao de antes. Escolher a coluna (em
vez de extrair das duas e comparar) mantém em duas operações JSON por linha.
`template_id` do nó WhatsApp/Web/SMS ficou intacto de propósito: não é
whereProperties e lê `properties` legitimamente.
Performed e LastPerformed carregavam switches DUPLICADOS para montar essa condição,
e eles estavam fora de sincronia: o do LastPerformed não listava `GreaterThan`,
`GreaterThanOrEqual`, `LessThan`, `LessThanOrEqual` nem `NotExists`, que caíam no
`default` e viravam IGUALDADE — também em silêncio, porque o SQL seguia válido
("score > 10" selecionava score == 10). Em vez de completar a lista da cópia, o
filtro virou um método só (`buildEventPropertyCondition`) usado pelos dois nós: a
causa era a duplicação. Operador desconhecido continua caindo em igualdade, mas
agora loga warning em vez de degradar calado.
Testes:
- Spec do builder (35 exemplos) fixando o SQL por operador, o escaping de path e
valor, a não-regressão do template_id, e um bloco de PARIDADE que exige condição
idêntica entre Performed e LastPerformed nos 10 operadores — é o teste que quebra
se a cópia for recriada.
- Teste de integração (test/segment-where-properties.e2e-spec.ts, 8 exemplos) que
EXECUTA a condição gerada contra um ClickHouse real e assere os CONTATOS
retornados, que é o critério de aceite do card. O spec unitário não pega este
bug: a expressão antiga é SQL válido, o que falhava era o casamento. Por isso um
dos exemplos roda a expressão ANTIGA contra as mesmas linhas e prova que ela erra
nas duas direções — o teste não é tautológico.
Provas negativas: revertendo a leitura de traits, 18 dos 20 exemplos originais
falham e 6 dos 8 do e2e; reintroduzindo o switch reduzido do LastPerformed, falham
exatamente os 9 que cobrem os operadores numéricos e a paridade.
Notas do teste de integração:
- FALHA se o ClickHouse não estiver acessível, em vez de se auto-pular. Um skip
condicional deixaria a suíte VERDE sem ter exercitado nada — o mesmo modo de
falhar silencioso que este commit corrige. (E `it.skip` por flag nem funcionaria:
o Jest avalia isso na coleta, antes do beforeAll.)
- Usa uma tabela ESPELHO (`CREATE TABLE ... AS contact_events`), criada e destruída
pelo teste. `clickhouse.service.ts` cria a `events_to_journey_triggers_mv`
escrevendo num engine Kafka SEMPRE, inclusive onde o broker é RabbitMQ
(`BROKER_TYPE=rabbitmq`, o compose community); sem Kafka atendendo, o INSERT na
tabela real fica preso até o timeout. A espelho mantém o teste determinístico nos
dois ambientes, com schema idêntico e sem tocar na tabela de produção.
- `async_insert: 0` fixado no cliente: o ClickHouse do community traz
`async_insert=1` no users.xml e o do ecosystem fica no default 0; com
`wait_for_async_insert=1` (ligado nos dois) o cliente ficaria preso ao flush do
buffer.
Validado nos dois ambientes, 8/8 em cada:
community :18123 ClickHouse 26.7 broker rabbitmq
ecosystem :18124 ClickHouse 25.8 broker kafka
Além do e2e, a expressão foi conferida direto na `contact_events` REAL do ecosystem
(sem espelho): properties={} / traits={"labelName":"VIP"}, expressão antiga devolve
vazio e a corrigida devolve VIP — o bug e o fix reproduzidos na tabela de produção,
com Kafka e MVs ativos.
Suíte completa: 1099 exemplos passando. As 4 suites que falham são pré-existentes
(campaigns.controller + 3 nodes temporal/evoai) e falham igual sem este commit.
tsc --noEmit limpo.
…ça os processadores real-time (CRM-241) Achados do code review do PR #118. O filtro de propriedade de evento vira um só, em `SegmentQueryUtils` (`extractEventProperty` + `buildEventPropertyCondition`). O builder do ClickHouse delega: os 35 exemplos do spec seguem passando sem qualquer ajuste, o que prova que o SQL emitido é idêntico. `atomic-processor.service.ts` (2 sítios) e `batch-processor.service.ts` carregavam mais duas cópias do MESMO filtro, lendo só `properties` — ou seja, o bug que o CRM-241 corrige, numa quarta e numa quinta superfície. São alcançáveis com `SEGMENT_COMPUTATION_TYPE=real-time`. Além da coluna errada, interpolavam `path`/`value`/`event`/`labelId` crus, sem escaping, e a cópia do batch lia `prop.key`/`prop.value` — shape que o front nunca emite, então o filtro resolvia para a string literal "undefined". As três passam a usar o builder compartilhado. O e2e passa a ser opt-in (`SEGMENT_E2E=1`), como o `tenant-isolation.e2e-spec.ts` da mesma pasta, para não somar uma suite falhando ao `npm run test:e2e`. A conexão vem das envs que o projeto já usa (`CLICKHOUSE_HOST`/`PORT`/`DATABASE`) em vez de `CLICKHOUSE_URL`/`CLICKHOUSE_DB` com default 18123, que não bate com o compose do próprio repo (8123). Ligado, ele continua FALHANDO — não pulando — se o servidor não responder. O `afterAll` disparava um segundo erro sem contexto quando o `beforeAll` falhava (o client já estava atribuído; `createClient` é lazy): agora só limpa o que criou, e varre tabelas espelho deixadas por uma run interrompida. Comentários novos em inglês e enxutos (CLAUDE.md), e o comentário que dizia que o path é interpolado três vezes estava errado: são duas (JSONHas + JSONExtractString). Testes: spec novo com 7 exemplos cobrindo os três sítios do módulo de processing (fallback para traits, escaping de aspas em path/valor/evento, e a não-regressão do shape errado do batch). Suíte completa: 1079 passando (era 1072); as 4 suites que falham são as mesmas pré-existentes (campaigns.controller + 3 nodes temporal/evoai). e2e 8/8 contra ClickHouse real, `tsc --noEmit` limpo.
…roperties-traits fix(segments): whereProperties precisa ler traits em evento de contato (CRM-241)
…-trigger-discriminates-event fix(journeys): gatilho de webhook casa o evento exato, nao o prefixo (CRM-256)
…ig do no (CRM-256) Retorno de code review sobre o #116, ja mesclado. O override de nome de evento saiu. Nenhuma UI de nó Webhook escreve esse campo — a WebhookConfiguration so tem URL, metodo e headers —, mas o editor copia data.eventName para conditions e metadata de TODO nó de gatilho (journeyFlowTriggers.ts) e o painel nao limpa o campo ao trocar o tipo (JourneyTriggerPanel.tsx:118). Um nó configurado como Evento e depois trocado para Webhook carregava o nome antigo, e o handler passava a casar esse nome em vez de webhook.journey_trigger: a jornada disparava no evento errado, agora com contato real, e o caminho legitimo deixava de casar. Com o override foram junto o .trim() sobre valor vindo de jsonb — que estoura com eventName nao-string e aborta a analise da mensagem para todas as jornadas, nao so a mal configurada — e o wrapper decide(), que so logava. O doc passa a registrar tambem o nó Aguardar evento -> Webhook: mesmo handler, roteado por eventType e avaliado com journey vazio, config sem nome de evento, logo satisfeito so por webhook.journey_trigger — hoje, por nada. Sem enableFallback essa espera nao tem timeout. Testes: as assercoes das fontes de config dao lugar a duas que provam o buraco fechado — um nó com o nome herdado nos tres lugares nao casa esse nome e continua casando webhook.journey_trigger.
…-trigger-fixed-target fix(journeys): gatilho de webhook casa so o evento fixo, sem ler config do no (CRM-256)
…(CRM-271) Retorno de code review sobre o PR #117. O descarte por evento era logado em debug, e CustomLoggerService.debug retorna antes do console e antes do winston — a linha nao saia em lugar nenhum. O criterio de aceite pede descarte visivel, e o que restava era o total corrido a cada mil, que nao nomeia evento algum: os 999 primeiros ficavam silenciosos. O teste que cobria isso afirmava contra o mock de logger.debug, entao seguia verde com a producao muda. O descarte passa a log/info. O volume em info nao muda: processMessage ja emite duas linhas por mensagem (uma delas com o evento inteiro serializado) e analyzeEventForJourneyTriggers uma terceira. Essa terceira anunciava analise para evento que seria descartado — a guarda subiu para antes dela, entao o evento descartado gasta uma linha a menos, nao uma a mais. Quando o que falta e o nome, a mensagem virava "Skipping event undefined"; passa a <unnamed>, e o contexto carrega messageId e anonymousId, que para as linhas de entregabilidade e a unica alca de volta ao evento de origem. O total corrido so imprimia em multiplos exatos do intervalo, entao ate 999 descartes se perdiam a cada restart; onModuleDestroy passa a liberar o saldo.
…mpty-contact-id fix(journeys): evento sem contato nao inicia execucao de jornada (CRM-271)
O metodo montava um evento webhook.received completo e nunca o publicava, e sanitizeHeaders so era usado por ele. Quem lia esse trecho procurando o caminho do webhook concluia que a ingestao existia.
A remocao do metodo morto tira a pista falsa e nao deixa nada no lugar. O README passa a dizer qual e o unico gatilho de webhook do modulo e que o POST /webhooks/* e outro pipeline.
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
… (CRM-257) O endpoint nao publica o webhook.journey_trigger em lugar nenhum: monta o evento e passa para startJourney como payload do workflow, sem tocar o barramento journey-triggers. Dizer "publishes" contradiz o docs/journey-manual-trigger.md, que ja documenta o caminho certo — a secao agora aponta para ele em vez de reescreve-lo. O trecho do POST /webhooks/* tambem afirmava isolamento que nao existe: a MV events_to_journey_triggers_mv encaminha toda linha de contact_events para o barramento, e o que segura os callbacks de provider sao os dois guards (contact_id vazio e match por nome exato), nao o pipeline ser outro.
…e-dead-webhook-trigger chore(journeys): remove processWebhookTrigger sem callers (CRM-257)
The note itself documents runtime behaviour and stays; only the link to the internal issue tracker is removed.
…link docs(broker): drop the tracker link from the redelivery note
Contributor
There was a problem hiding this comment.
Sorry @gomessguii, your pull request is larger than the review limit of 150,000 diff characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Corte de release: leva a
developpara amain— 40 commits, 9 cards.Faz parte da cascata que leva o evolution-ecosystem para produção, parada em 2 de julho. A
maindo superprojeto aponta para SHAs que estão namainde cada submódulo, então cada repositório corta primeiro; o superprojeto bumpa os ponteiros por último.Merge testado com
git merge-treeantes de abrir: sem conflitos. Amainestá 2 commits à frente da develop; o merge preserva os dois lados.Este repositório não depende dos demais dentro da cascata — pode ser mergeado em qualquer ordem em relação aos irmãos.
As migrations do conjunto foram ensaiadas contra uma cópia restaurada do banco de produção (Postgres 18.6, imagens por digest): cinco passos do
migrate-job, todos exit 0, dado intacto, idempotência confirmada.Runbook: https://claude.ai/code/artifact/3a9608d8-4126-4c9c-9934-8e0e9074fbff