Skip to content

Normalize cache keys on getWorkflow to workflow id#3604

Open
vegaro wants to merge 2 commits into
mainfrom
cesar/workflow-offering-id-cache-miss
Open

Normalize cache keys on getWorkflow to workflow id#3604
vegaro wants to merge 2 commits into
mainfrom
cesar/workflow-offering-id-cache-miss

Conversation

@vegaro

@vegaro vegaro commented Jun 15, 2026

Copy link
Copy Markdown
Member

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-ios and hybrids

Motivation

The render path asks for a workflow by offering id (the backend lazily converts it into a workflow), while the prefetch path asks by workflow id. WorkflowManager.getWorkflow's workflowId parameter was therefore overloaded — sometimes a workflow id, sometimes an offering id — and the cache was keyed inconsistently. A workflow prefetched under its workflow id missed when the render later asked by offering id, and a lazily-converted workflow cached under the offering id missed once the workflows list populated the offeringId → workflowId map. Either way: a redundant fetch on every render.

Description

getWorkflow now normalizes the incoming identifier to the canonical workflow id (via the offeringId → workflowId map) before the cache read, so an ask by either id resolves to the same entry. The parameter is renamed to workflowOrOfferingId to make that contract honest. An as-yet-unconverted offering id has no mapping and passes through; its real id is learned from the response.

On a successful resolve the result is cached once, under the resolved workflow's own id (via a shared cacheResolvedWorkflow helper), and any newly discovered offeringId → workflowId mapping is recorded (new WorkflowsCache.recordWorkflowIdForOfferingId, copy-on-write under the existing lock) so later asks by offering id resolve to it. A response that resolves with an empty id can't be keyed in the cache, so it's delivered for the current render but not cached under a wrong key — the next render refetches. The disk-fallback path (resolveDiskFallback) caches the recovered envelope through the same helper, so it keys off the resolved id too.

No public API change. Unit tests cover: ask-by-offering-id hitting the entry prefetched under the workflow id; the lazy-conversion single-entry caching + mapping record + cache-hit on the next offering-id ask; the empty-id response being delivered but not cached; the disk-fallback re-pin keying off the resolved id; and the WorkflowsCache map record/replace behavior.

Follow-ups (not in this PR)

  • Mirror this gating to purchases-ios and hybrids.

Note

Medium Risk
Touches paywall workflow fetch/cache behavior (stale-while-revalidate and disk fallback), but changes are localized, backward-compatible at the public API, and covered by new unit tests.

Overview
Fixes redundant workflow fetches when the render path uses an offering id but prefetch/cache used a workflow id (or the opposite after lazy conversion).

WorkflowManager.getWorkflow now takes workflowOrOfferingId, resolves it through the existing offering→workflow map before any cache read, and centralizes post-resolve storage in cacheResolvedWorkflow: one in-memory entry under the resolved workflow id, optional recordWorkflowIdForOfferingId when the request id differed, and no cache write when the resolved id is empty. Disk fallback re-pins through the same helper so keys stay consistent.

No public SDK API change; orchestrator still exposes workflowId but forwards the value as workflowOrOfferingId. Tests cover prefetch-by-workflow-id vs render-by-offering-id, lazy conversion mapping, empty-id responses, and disk fallback keying.

Reviewed by Cursor Bugbot for commit 5d985a7. Bugbot is set up for automated code reviews on this repo. Configure here.

@vegaro vegaro added the pr:fix A bug fix label Jun 15, 2026
…ache misses

When getWorkflow is called with an offering ID (no map entry yet), the backend
lazily converts it and returns a workflow with a different real workflow ID. The
result was only cached under the offering ID, so after the list was fetched and
the offeringId→workflowId map populated, lookups by the real ID missed.

Now the resolved result is cached once, under result.workflow.id, and the
discovered offeringId→workflowId mapping is recorded via a new
WorkflowsCache.recordWorkflowIdForOfferingId so the next workflowIdForOfferingId
lookup resolves to that same single cache entry — instead of keying one workflow
under two keys that then drift on invalidation/SWR refresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vegaro vegaro force-pushed the cesar/workflow-offering-id-cache-miss branch from 4f6ccbf to 7d15161 Compare June 15, 2026 14:48
@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.27%. Comparing base (3dd8902) to head (5d985a7).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3604      +/-   ##
==========================================
+ Coverage   80.26%   80.27%   +0.01%     
==========================================
  Files         378      378              
  Lines       15448    15456       +8     
  Branches     2143     2146       +3     
==========================================
+ Hits        12400    12408       +8     
  Misses       2189     2189              
  Partials      859      859              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vegaro vegaro force-pushed the cesar/workflow-offering-id-cache-miss branch from 3ecf84a to 297b27b Compare June 15, 2026 15:47
@vegaro vegaro changed the title fix: cache lazy-converted workflow under its resolved workflow ID fix: resolve offering ids to workflow ids in getWorkflow to prevent cache misses Jun 15, 2026
@vegaro vegaro added pr:other and removed pr:fix A bug fix labels Jun 15, 2026
@vegaro vegaro changed the title fix: resolve offering ids to workflow ids in getWorkflow to prevent cache misses Normalize cache keys on getWorkflow to workflow id Jun 15, 2026
…ache misses

The render path asks for a workflow by offering id (the backend lazily converts
it), while prefetch asks by workflow id. getWorkflow's `workflowId` parameter was
therefore overloaded, and the cache was keyed inconsistently: a workflow
prefetched under its workflow id missed when later requested by offering id, and
vice versa.

WorkflowManager.getWorkflow now normalizes the incoming identifier through the
offeringId→workflowId map to the canonical workflow id before the cache read, so
an ask by either id hits the same entry (parameter renamed to
`workflowOrOfferingId` to make the contract honest). On a successful resolve the
result is cached once, under the resolved workflow's own id (via a shared
cacheResolvedWorkflow helper), and any newly discovered offeringId→workflowId
mapping is recorded (new WorkflowsCache.recordWorkflowIdForOfferingId) so
subsequent asks by offering id resolve to it. A response that resolves with an
empty id can't be keyed, so it is delivered for the current render but not cached
under a wrong key. The disk-fallback path (resolveDiskFallback) caches the
recovered envelope through the same helper, keying it off the resolved id too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vegaro vegaro force-pushed the cesar/workflow-offering-id-cache-miss branch from 297b27b to 5d985a7 Compare June 15, 2026 16:06
@vegaro vegaro marked this pull request as ready for review June 15, 2026 16:06
@vegaro vegaro requested a review from a team as a code owner June 15, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant