fix(evo-flow): preserve journey variables across the cache + refresh on write (EVO-1836)#77
Conversation
…on write (EVO-1836) The journey cache reconstruction in findOne/findAll/findActive dropped the `variables` field (the cache stored it, but the hand-rebuilt object omitted it), so getJourneyVariables returned [] once a journey was cached. updateJourneyVariables also saved to the DB without refreshing the cache. Include `variables` in all three cache reconstructions, and cache.set the saved journey after a variables update (instead of invalidate, which dropped it from the active/list index used by the trigger processor). Adds a regression spec asserting findOne preserves cached variables. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reviewer's GuideEnsures journey variables are preserved when serving journeys from the in-memory cache and that the cache is refreshed (not invalidated) after variable updates, preventing stale or empty variables on cache hits. Sequence diagram for getJourneyVariables cache-hit preserving variablessequenceDiagram
actor Client
participant JourneysService
participant JourneyCacheService
Client->>JourneysService: getJourneyVariables(journeyId)
JourneysService->>JourneyCacheService: get(journeyId)
JourneyCacheService-->>JourneysService: CachedJourney(with variables)
JourneysService-->>Client: variables (cachedJourney.variables)
Sequence diagram for updateJourneyVariables refreshing cachesequenceDiagram
actor Client
participant JourneysService
participant JourneyRepository
participant JourneyCacheService
Client->>JourneysService: updateJourneyVariables(id, variables)
JourneysService->>JourneyRepository: save(journey)
JourneyRepository-->>JourneysService: updatedJourney(with variables)
JourneysService->>JourneyCacheService: set(updatedJourney)
JourneysService-->>Client: updatedJourney.variables || []
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 found 1 issue, and left some high level feedback:
- The cache-to-Journey reconstruction logic is now duplicated in three places with the new
variablesmapping; consider extracting a shared helper to reduce the chance of future field omissions or inconsistencies. - In
getJourneyVariablesthe behavior now relies oncached.variablesbeing defined; if callers expect an array, you might want to normalize with a default (e.g.,cached.variables ?? []) when writing to or reading from the cache. - The new spec spies on
(service as any).logger, which couples the test to a private implementation detail; consider exposing a logger via constructor injection or a dedicated method so tests can interact with it withoutany/private access.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The cache-to-Journey reconstruction logic is now duplicated in three places with the new `variables` mapping; consider extracting a shared helper to reduce the chance of future field omissions or inconsistencies.
- In `getJourneyVariables` the behavior now relies on `cached.variables` being defined; if callers expect an array, you might want to normalize with a default (e.g., `cached.variables ?? []`) when writing to or reading from the cache.
- The new spec spies on `(service as any).logger`, which couples the test to a private implementation detail; consider exposing a logger via constructor injection or a dedicated method so tests can interact with it without `any`/private access.
## Individual Comments
### Comment 1
<location path="src/modules/journeys/journeys.service.ts" line_range="419-423" />
<code_context>
journey.variables = variables;
- await this.journeyRepository.save(journey);
+ const updatedJourney = await this.journeyRepository.save(journey);
- return journey.variables || [];
+ await this.journeyCacheService.set(updatedJourney);
+
+ return updatedJourney.variables || [];
} catch (error) {
this.logger.error(
</code_context>
<issue_to_address>
**question (bug_risk):** Clarify whether a cache write failure after a successful DB save should fail the whole updateVariables call.
Because `journeyCacheService.set` now runs after a successful DB save, a transient cache outage will cause `updateJourneyVariables` to throw even though the write has committed. This can produce confusing errors and make retries unsafe. If strict write-through semantics aren’t required, consider wrapping the cache write in a try/catch, logging failures, and still returning `updatedJourney.variables`. If strict DB–cache consistency is intentional, then keeping the current behavior is fine but should be confirmed with the expected semantics.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const updatedJourney = await this.journeyRepository.save(journey); | ||
|
|
||
| return journey.variables || []; | ||
| await this.journeyCacheService.set(updatedJourney); | ||
|
|
||
| return updatedJourney.variables || []; |
There was a problem hiding this comment.
question (bug_risk): Clarify whether a cache write failure after a successful DB save should fail the whole updateVariables call.
Because journeyCacheService.set now runs after a successful DB save, a transient cache outage will cause updateJourneyVariables to throw even though the write has committed. This can produce confusing errors and make retries unsafe. If strict write-through semantics aren’t required, consider wrapping the cache write in a try/catch, logging failures, and still returning updatedJourney.variables. If strict DB–cache consistency is intentional, then keeping the current behavior is fine but should be confirmed with the expected semantics.
dpaes
left a comment
There was a problem hiding this comment.
Review — EVO-1836 (approved ✅)
Independent cross-repo verification (evo-ai-frontend-community #168 + this PR). Verdict: correct and complete, no blockers.
Both fixes verified.
findOne/findAll/findActivenow includevariablesin the cache-hit reconstruction. Confirmed these are the only 3 hand-rebuildas Journeysites in the repo; the Temporal activities consumeCachedJourneydirectly (not lossy). TheJourneyentity has no tenant/account column, so the reconstruction enumerates every persisted column → no data-loss risk whenupdateJourneyVariablessaves the cache-hit object (samefindOne → save → setpattern already used byupdate()/toggleActive()in production).invalidate() → set()inupdateJourneyVariables: correct, and it fixes a real latent bug beyond the display issue.invalidatedidsremon the:indexset; sincegetAll()only falls back to the DB when the whole index is empty, an edited journey was silently dropped fromfindActive()/ the trigger processor until the 12h TTL expired.set()rewrites the body (with fresh variables) and re-adds the id to:indexin one shot.
Non-blocking note. The headline invalidate → set switch (the AC4 "reopen → still there" mechanism) has no test — reverting that line would pass all current evo-flow specs. The findOne cache-hit fix is genuinely pinned (the added spec fails on revert; constructor arg order is correct). Given Sourcery-only CI, consider a regression test asserting set() (not invalidate) is called after save.
Approving + squash-merging.
Summary
Part of EVO-1836 (cross-repo — journey variables broken). The backend root cause of "variables never display": the journey cache reconstruction dropped
variables.journey-cache.service.tsstoresvariables(it's onCachedJourney+ theset()mapping), butjourneys.service.tsfindOne()/findAll()/findActive()hand-rebuild theJourneyfrom cache and omittedvariables. So once a journey was cached (editor load),getJourneyVariablesdidjourney.variables || []→[].updateJourneyVariablessaved to the DB but never refreshed the cache → stale[]served forever.Fix:
variables: cached.variablesin all three cache reconstructions (findOne,findAll,findActive).updateJourneyVariables:cache.set(savedJourney)after save (matchingupdate()/toggleActive()), instead ofinvalidate()— review foundinvalidate()removed the journey from the active/list index, dropping it fromfindActive()(event trigger matching) andfindAll()until re-fetched.Validation
npm run build→ cleannpx jest journeys.service.spec→ 5/5 (incl. a regression spec assertingfindOnepreserves cached variables)npx prettier --check→ new lines clean (pre-existingany/format debt untouched)POST /journeys/:id/variables→GETreturns the variable, including on a cache hit.invalidate→setregression and thefindActiveomission were caught and fixed here; partial-save data-loss refuted.Changed Files
src/modules/journeys/journeys.service.tssrc/modules/journeys/journeys.service.spec.tsRelated PRs
Linked Issue
🤖 Generated with Claude Code
Summary by Sourcery
Preserve journey variables when serving cached journeys and refresh the cache after variable updates.
Bug Fixes:
Tests: