Skip to content

test: caller-driven replica-lag + idempotency guards (stacked on #4284)#4285

Merged
d-cs merged 7 commits into
mainfrom
fix/wait-until-idempotency-retry-rewait-tests
Jul 19, 2026
Merged

test: caller-driven replica-lag + idempotency guards (stacked on #4284)#4285
d-cs merged 7 commits into
mainfrom
fix/wait-until-idempotency-retry-rewait-tests

Conversation

@d-cs

@d-cs d-cs commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #4284 — tests only

This PR contains only the tests that guard the production fixes in #4284 (its base). Review #4284 first; this branch adds no production code.

What

Caller-driven replica-lag and idempotency guards for every fixed site:

  • Each guard drives the real exported caller (route loader/action, presenter .call(), service, or engine method) against a real Postgres with the owning replica frozen via the shared laggingReplica testcontainer primitive — never a store-seam reimplementation.
  • For a fixed site the guard goes RED when the production change is reverted; for a tolerated read-view site it's a caller-driven GREEN proof the miss self-heals (returns null/empty, no mutation, row live on primary).
  • The global-scope idempotency guard drives the real dedup + claim path through a real MollifierBuffer over a Redis testcontainer (real SETNX/poll/publish), and covers the cross-DB andWait waitpoint wiring and the expired/failed clear-and-recreate reacquire cases.

Run with vitest --no-file-parallelism (testcontainers). Verified GREEN, and revert→RED verified per fixed site.

…bal-scope idempotency correctness under the run-ops split

Production code only — the guarding tests are in the stacked PR.

1) Read-your-writes: run-store reads that gate a mutation or feed a public GET /
   realtime response were routed to a lagging read replica, so a just-written
   run/waitpoint/batch could spuriously miss under replica lag. Route those reads
   to the owning primary (findRun/findWaitpoint/findBatchTaskRunByFriendlyId ->
   *OnPrimary, a primary re-read on a miss, or a retryable 404 where the SDK polls).
   Additive: the happy path is unchanged; a primary read happens only on a miss.

2) Global-scope idempotency across the split: a global-scope key carries no per-run
   salt, so the same (env, task, key) triggered concurrently from parents resident
   on different run-ops DBs could dedup-miss on each DB and create a duplicate run
   (the per-DB unique index can't enforce cross-DB uniqueness). Serialize such
   triggers (global scope, or scope-absent, while split is active) through the
   existing Redis idempotency claim, resolve the winner by id across both DBs, and
   reacquire the claim on the expired/failed clear-and-recreate path. run/attempt
   scope embed the run id in their hash and never contend.
@changeset-bot

changeset-bot Bot commented Jul 18, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d9f098c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8e948999-af21-4bb4-8a66-6f55956acc1f

📥 Commits

Reviewing files that changed from the base of the PR and between 7102b75 and d9f098c.

📒 Files selected for processing (62)
  • apps/webapp/test/batchServices.replicaLag.test.ts
  • apps/webapp/test/bulkActionV2.replicaLag.test.ts
  • apps/webapp/test/cancelRouteReplicaLag.guard.test.ts
  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/engineReplicaReads.replicaLag.guard.test.ts
  • apps/webapp/test/findEnvironmentFromRunReplicaLag.guard.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/idempotencyGlobalScopeCrossDbConcurrent.test.ts
  • apps/webapp/test/idempotencyResetRouteReplicaLag.guard.test.ts
  • apps/webapp/test/metadataRouteReplicaLag.guard.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/presentersSessionBatchReplicaLag.guard.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/readRunForEvent.replicaLag.test.ts
  • apps/webapp/test/realtimeServices.replicaLag.test.ts
  • apps/webapp/test/realtimeSessionsIoRoute.replicaLag.guard.test.ts
  • apps/webapp/test/realtimeStreamRoutes.replicaLag.test.ts
  • apps/webapp/test/replayRouteReplicaLag.guard.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
  • apps/webapp/test/runGetRoutes.replicaLag.guard.test.ts
  • apps/webapp/test/runPresenters.replicaLag.test.ts
  • apps/webapp/test/runsRepositoryConvert.replicaLag.test.ts
  • apps/webapp/test/sessionWaitpointRoutes.replicaLag.guard.test.ts
  • apps/webapp/test/spanTraceRoutes.replicaLag.test.ts
  • apps/webapp/test/waitpointCallbackRouteReplicaLag.guard.test.ts
  • apps/webapp/test/waitpointCompleteRouteReplicaLag.guard.test.ts
  • apps/webapp/test/waitpointPresenters.replicaLag.guard.test.ts
  • internal-packages/run-engine/src/engine/retryDecisionReadAfterWrite.replicaLag.test.ts
  • internal-packages/run-engine/src/engine/systems/ttlSystemExpireReplicaLag.guard.test.ts
  • internal-packages/run-engine/src/engine/tests/concurrencySweeperCallbackReplicaLag.guard.test.ts
  • internal-packages/run-engine/src/engine/tests/runAttemptSystemReplicaLag.guard.test.ts
  • internal-packages/run-store/src/runOpsStore.batchDependentAttemptReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.batchIdempotencyDedupReadAfterWrite.test.ts
  • internal-packages/run-store/src/runOpsStore.bulkActionReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.cancelRunReadAfterWrite.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.dashboardAgentReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.engineReadViews.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.idempotencyGlobalScopeCrossDb.test.ts
  • internal-packages/run-store/src/runOpsStore.idempotencyResetReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.modelRuntimeEnvReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.presentersRunReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.presentersSessionBatchReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.presentersWaitpointReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.realtimeServicesReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.replayReadAfterWrite.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.resolveRunForMutationReplicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.routesBatchGetReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.routesRealtimeStreamReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.routesRunGetReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.routesSpanTraceReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.serviceBatchFamilyReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.sessionMetadataRouteReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.sessionRunProbeReadAfterWrite.test.ts
  • internal-packages/run-store/src/runOpsStore.storeReceiverReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.test.ts
  • internal-packages/run-store/src/runOpsStore.ttlExpireOrphanReplicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.usageCostUndercountReadAfterWrite.test.ts
  • internal-packages/run-store/src/runOpsStore.waitpointCompleteRouteAndReplayLoaderReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.waitpointCompleteTokenReadAfterWrite.test.ts
  • internal-packages/run-store/src/runOpsStore.waitpointDedupReadAfterWrite.test.ts
🚧 Files skipped from review as they are similar to previous changes (55)
  • internal-packages/run-engine/src/engine/systems/ttlSystemExpireReplicaLag.guard.test.ts
  • internal-packages/run-store/src/runOpsStore.sessionRunProbeReadAfterWrite.test.ts
  • internal-packages/run-store/src/runOpsStore.usageCostUndercountReadAfterWrite.test.ts
  • internal-packages/run-engine/src/engine/retryDecisionReadAfterWrite.replicaLag.test.ts
  • apps/webapp/test/readRunForEvent.replicaLag.test.ts
  • apps/webapp/test/runsRepositoryConvert.replicaLag.test.ts
  • apps/webapp/test/engineReplicaReads.replicaLag.guard.test.ts
  • internal-packages/run-store/src/runOpsStore.dashboardAgentReadView.replicaLag.test.ts
  • apps/webapp/test/waitpointCallbackRouteReplicaLag.guard.test.ts
  • internal-packages/run-store/src/runOpsStore.cancelRunReadAfterWrite.replicaLag.test.ts
  • apps/webapp/test/realtimeServices.replicaLag.test.ts
  • apps/webapp/test/sessionWaitpointRoutes.replicaLag.guard.test.ts
  • apps/webapp/test/cancelRouteReplicaLag.guard.test.ts
  • apps/webapp/test/findEnvironmentFromRunReplicaLag.guard.test.ts
  • internal-packages/run-store/src/runOpsStore.batchIdempotencyDedupReadAfterWrite.test.ts
  • internal-packages/run-store/src/runOpsStore.routesSpanTraceReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.waitpointCompleteTokenReadAfterWrite.test.ts
  • internal-packages/run-store/src/runOpsStore.modelRuntimeEnvReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.waitpointDedupReadAfterWrite.test.ts
  • internal-packages/run-store/src/runOpsStore.idempotencyResetReadView.replicaLag.test.ts
  • internal-packages/run-engine/src/engine/tests/concurrencySweeperCallbackReplicaLag.guard.test.ts
  • internal-packages/run-store/src/runOpsStore.routesRunGetReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.sessionMetadataRouteReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.test.ts
  • internal-packages/run-store/src/runOpsStore.waitpointCompleteRouteAndReplayLoaderReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.engineReadViews.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.ttlExpireOrphanReplicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.presentersSessionBatchReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.idempotencyGlobalScopeCrossDb.test.ts
  • apps/webapp/test/realtimeSessionsIoRoute.replicaLag.guard.test.ts
  • apps/webapp/test/bulkActionV2.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.batchDependentAttemptReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.bulkActionReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.presentersWaitpointReadView.replicaLag.test.ts
  • apps/webapp/test/spanTraceRoutes.replicaLag.test.ts
  • apps/webapp/test/waitpointCompleteRouteReplicaLag.guard.test.ts
  • internal-packages/run-store/src/runOpsStore.storeReceiverReadView.replicaLag.test.ts
  • apps/webapp/test/runPresenters.replicaLag.test.ts
  • apps/webapp/test/idempotencyGlobalScopeCrossDbConcurrent.test.ts
  • apps/webapp/test/metadataRouteReplicaLag.guard.test.ts
  • apps/webapp/test/idempotencyResetRouteReplicaLag.guard.test.ts
  • apps/webapp/test/replayRouteReplicaLag.guard.test.ts
  • internal-packages/run-store/src/runOpsStore.routesRealtimeStreamReadView.replicaLag.test.ts
  • internal-packages/run-engine/src/engine/tests/runAttemptSystemReplicaLag.guard.test.ts
  • apps/webapp/test/realtimeStreamRoutes.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.realtimeServicesReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.serviceBatchFamilyReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.routesBatchGetReadView.replicaLag.test.ts
  • apps/webapp/test/batchServices.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.presentersRunReadView.replicaLag.test.ts
  • internal-packages/run-store/src/runOpsStore.replayReadAfterWrite.replicaLag.test.ts
  • apps/webapp/test/presentersSessionBatchReplicaLag.guard.test.ts
  • apps/webapp/test/waitpointPresenters.replicaLag.guard.test.ts
  • apps/webapp/test/runGetRoutes.replicaLag.guard.test.ts
  • internal-packages/run-store/src/runOpsStore.resolveRunForMutationReplicaLag.test.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout. (17)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (12, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (3, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (9, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (2, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (10, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (4, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (1, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (11, 12)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (5, 12)
  • GitHub Check: internal / 🧪 Unit Tests: Internal
  • GitHub Check: runops-guard / runops-guard
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp
  • GitHub Check: code-quality / code-quality
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Prefer static imports over dynamic import(); use dynamic imports only for unresolvable circular dependencies, genuine performance code splitting, or conditional runtime loading.
Import Trigger.dev tasks from @trigger.dev/sdk; never use @trigger.dev/sdk/v3 or deprecated client.defineJob.
Add agentcrumbs while writing code using approved namespaces; mark lines with // @Crumbs or blocks with `// `#region` `@crumbs, and strip them before merging.

Files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use vitest for all tests in the Trigger.dev repository

**/*.{test,spec}.{ts,tsx}: Use Vitest exclusively and never mock dependencies; use Testcontainers for integration dependencies.
Place test files next to the source files they test.

Files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: Access environment variables through the env export of env.server.ts instead of directly accessing process.env
Use subpath exports from @trigger.dev/core package instead of importing from the root @trigger.dev/core path

Do not reintroduce the removed v1 execution path; RunEngineVersion.V1 branches may only reject or finalize gracefully so v3 clients receive a clean 4xx, never a 5xx.

Files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
apps/webapp/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Do not import env.server.ts directly or indirectly into test files; instead pass environment-dependent values through options/parameters to make code testable

Files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

For apps, use typecheck for verification and never use build as the correctness check.

Files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
apps/webapp/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

Test files must not import app/env.server.ts; pass configuration as options instead.

Files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
🧠 Learnings (18)
📚 Learning: 2026-03-22T13:26:12.060Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3244
File: apps/webapp/app/components/code/TextEditor.tsx:81-86
Timestamp: 2026-03-22T13:26:12.060Z
Learning: In the triggerdotdev/trigger.dev codebase, do not flag `navigator.clipboard.writeText(...)` calls for `missing-await`/`unhandled-promise` issues. These clipboard writes are intentionally invoked without `await` and without `catch` handlers across the project; keep that behavior consistent when reviewing TypeScript/TSX files (e.g., usages like in `apps/webapp/app/components/code/TextEditor.tsx`).

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-03-22T19:24:14.403Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3187
File: apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts:200-204
Timestamp: 2026-03-22T19:24:14.403Z
Learning: In the triggerdotdev/trigger.dev codebase, webhook URLs are not expected to contain embedded credentials/secrets (e.g., fields like `ProjectAlertWebhookProperties` should only hold credential-free webhook endpoints). During code review, if you see logging or inclusion of raw webhook URLs in error messages, do not automatically treat it as a credential-leak/secrets-in-logs issue by default—first verify the URL does not contain embedded credentials (for example, no username/password in the URL, no obvious secret/token query params or fragments). If the URL is credential-free per this project’s conventions, allow the logging.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma error P1001 ("Can't reach database server") in TypeScript, don’t assume a single error shape. Prisma can surface P1001 via two different error classes/fields: `PrismaClientKnownRequestError` exposes it as `err.code === "P1001"` (common during mid-query connection drops), while `PrismaClientInitializationError` exposes it as `err.errorCode === "P1001"` (common on client startup failure). Therefore, predicates should use `err.code === "P1001" || err.errorCode === "P1001"`. Do not flag `err.code === "P1001"` as “unreachable/never matches,” as it is expected in production.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma errors for P1001 ("Can't reach database server"), do not assume it only appears under a single property name. Prisma may surface P1001 via either `PrismaClientKnownRequestError` (`err.code === "P1001"`, e.g., mid-query connection drops) or `PrismaClientInitializationError` (`err.errorCode === "P1001"`, e.g., client startup connection failure). To reliably detect the condition, check `err.code === "P1001" || err.errorCode === "P1001"`, and avoid review rules that would incorrectly flag `err.code === "P1001"` as unreachable/never-matching.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-06-13T19:53:13.759Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3937
File: packages/trigger-sdk/skills/realtime-and-frontend/SKILL.md:258-260
Timestamp: 2026-06-13T19:53:13.759Z
Learning: When reviewing code that uses `trigger.dev/react-hooks`’s `useRealtimeRun`, preserve the call signature where the first argument is the full realtime handle object (not `handle.id`). This is intentional to maintain type-safety and is consistent with the official docs; do not suggest changing the first argument from the handle object to `handle.id`.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-06-17T17:13:49.929Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3948
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx:48-62
Timestamp: 2026-06-17T17:13:49.929Z
Learning: In triggerdotdev/trigger.dev, within `dashboardLoader`/`dashboardAction` (or similar context resolver code) whenever you resolve an organization ID from an organization slug for RBAC/enterprise authorization scope, always read from the primary Prisma client (`prisma`), not `$replica`. Using `$replica` can hit replica-lag and cause the RBAC lookup/authorization to run without the correct org scope (bypassing intended role enforcement). Implement the slug→org lookup with `prisma.organization.findFirst(...)` (or equivalent primary-client query) and add an inline comment documenting why the primary client is required (replica lag could lead to unscoped RBAC checks).

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-06-23T13:04:21.413Z
Learnt from: carderne
Repo: triggerdotdev/trigger.dev PR: 4023
File: apps/webapp/app/services/upsertBranch.server.ts:14-18
Timestamp: 2026-06-23T13:04:21.413Z
Learning: In TypeScript, it’s valid to `import { type X }` and then use `typeof X` in a type-only position, e.g. `type Alias = z.infer<typeof X>`. The `type` modifier suppresses the runtime import, but the type checker still has the full exported type so `z.infer<typeof X>` can resolve correctly. In code reviews, don’t flag this as a TypeScript compile error as long as `typeof X` is used in a type context (e.g., with `z.infer`, `type` aliases, generics), not as a runtime value.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-05-07T12:25:18.271Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3531
File: apps/webapp/test/sentryTraceContext.server.test.ts:9-47
Timestamp: 2026-05-07T12:25:18.271Z
Learning: In the triggerdotdev/trigger.dev webapp test suite, it is acceptable to leave `createInMemoryTracing()` calls that register a global `NodeTracerProvider` without `afterEach`/`afterAll` teardown. Do not flag this as a test-ordering risk when the code follows the established pattern used across webapp tests (e.g., replication service/benchmark/backfiller tests). This is considered safe because `trace.getActiveSpan()` when called outside a `context.with(...)` block reads `AsyncLocalStorage.getStore()` (undefined when no `run()` scope exists), so it falls back to `ROOT_CONTEXT` with no attached span—regardless of which provider is registered.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-05-28T20:02:10.647Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 3772
File: apps/webapp/test/findOrCreateBackgroundWorker.test.ts:1-1
Timestamp: 2026-05-28T20:02:10.647Z
Learning: In the triggerdotdev/trigger.dev monorepo, for the `apps/webapp` package use the established convention of storing Vitest tests (unit, integration, and e2e) under `apps/webapp/test/` rather than colocating them next to source files. Do not flag files located in `apps/webapp/test/` as violating any rule that says to colocate tests with source.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-05-12T21:04:05.815Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3542
File: apps/webapp/app/components/sessions/v1/SessionStatus.tsx:1-3
Timestamp: 2026-05-12T21:04:05.815Z
Learning: In this Remix + TypeScript codebase, do not flag a server/client boundary violation when a file imports only types from a module matching `*.server`.

Specifically, it’s safe to import types using `import type { Foo } from "*.server"` or `import { type Foo } from "*.server"` because TypeScript erases type-only imports at compile time and they emit no JavaScript, so they won’t cross the Remix server/client bundle boundary.

Only raise the boundary concern for value imports (e.g., `import { Foo }` without `type`, or `import Foo`), since those produce JavaScript output.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-06-25T18:21:51.905Z
Learnt from: carderne
Repo: triggerdotdev/trigger.dev PR: 4039
File: apps/webapp/app/routes/invite-revoke.tsx:0-0
Timestamp: 2026-06-25T18:21:51.905Z
Learning: During the Zod v4 migration in the triggerdotdev/trigger.dev webapp, ensure any imports from `conform-to/zod` use the Zod-4 subpath: `conform-to/zod/v4` (e.g., `import { parseWithZod } from "conform-to/zod/v4"`). Do not import from the package root `conform-to/zod`, because it is the Zod 3 implementation and may load Zod-3-only symbols (e.g., `ZodBranded`, `ZodEffects`), which can throw at module load (notably with `zod4.4.3`). This should be enforced across `apps/webapp/**/*` where helpers like `parseWithZod` and `conformZodMessage` are used.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-07-03T17:10:21.498Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 4148
File: apps/webapp/app/models/orgMember.server.ts:149-168
Timestamp: 2026-07-03T17:10:21.498Z
Learning: In triggerdotdev/trigger.dev, `User.email` (Prisma schema: `internal-packages/database/prisma/schema.prisma`) currently does NOT use `citext` and does NOT have a `lower(email)` functional unique index. Therefore, do not introduce Prisma queries like `where: { email: { equals: <value>, mode: "insensitive" } }` (or any case-insensitive lookup) against `User.email`, because it can force sequential scans of the `users` table under load. During review, ensure email is normalized (e.g., lowercased/trimmed) before both writes and subsequent lookups, and if true case-insensitive behavior/uniqueness is required, implement it via a separate app-wide migration (e.g., switch to `citext` and/or add a functional unique index with backfill) rather than bolting it onto individual feature PRs.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-05-18T14:40:02.173Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3658
File: packages/core/src/v3/realtimeStreams/manager.test.ts:1-147
Timestamp: 2026-05-18T14:40:02.173Z
Learning: In the triggerdotdev/trigger.dev repo, the policy “Never mock anything — use testcontainers instead” should only be enforced for integration tests that interact with real external services (e.g., Redis, Postgres) via actual infrastructure. For unit tests that exercise pure in-memory logic (e.g., cache semantics) it is OK to stub collaborators such as `ApiClient` using Vitest (`vi.fn()`) to assert call counts or control behavior. Do not flag `vi.fn()`-based `ApiClient` stubs in unit tests as violations of the testcontainers policy.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-06-04T18:16:35.386Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 3836
File: apps/supervisor/src/backpressure/backpressureMonitor.ts:3-5
Timestamp: 2026-06-04T18:16:35.386Z
Learning: When reviewing TypeScript in this repo, apply the rule “prefer type aliases over interfaces” only to data/object shapes and union/intersection type modeling. If an interface is being used as a behavioral contract for collaborators to implement (e.g., method-shape interfaces that define required behavior, such as `BackpressureLogger` / `BackpressureSignalSource` in `apps/supervisor/src/backpressure/backpressureMonitor.ts`), keep it as an `interface` and do not flag it as a type-alias-vs-interface violation.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-06-09T17:58:04.699Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 3879
File: apps/webapp/app/models/vercelIntegration.server.ts:619-630
Timestamp: 2026-06-09T17:58:04.699Z
Learning: In this codebase, outbound raw `fetch` calls should typically rely on Node/undici’s default request timeout (about ~300s) rather than adding a per-call `AbortController` + `setTimeout` wrapper inside individual functions (e.g. in files like `apps/webapp/app/models/vercelIntegration.server.ts`). During code review, do not flag the absence of a per-call timeout on a single `fetch` as an issue; if per-call timeouts are needed, they should be implemented via a codebase-wide convention (e.g., a shared fetch wrapper or documented pattern) rather than ad-hoc per-function changes.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-06-16T09:19:47.637Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3960
File: apps/webapp/test/prismaInfrastructureErrorCapture.test.ts:0-0
Timestamp: 2026-06-16T09:19:47.637Z
Learning: In this repo’s Vitest setup, `vitest.config.ts` uses `globals: true`, so identifiers like `vi`, `describe`, `it`, and `expect` are available as globals in Vitest test files. During code review, do not flag missing `vi`/`describe`/`it`/`expect` imports as a runtime error or correctness issue when they’re used in `*.test.ts/tsx` or `*.spec.ts/tsx` files. Explicit imports are still preferred for consistency, but they’re not required for runtime behavior.

Applied to files:

  • apps/webapp/test/claimTtl.test.ts
  • apps/webapp/test/publishClaimResult.test.ts
  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
  • apps/webapp/test/reacquireClearedGlobalWinner.test.ts
  • apps/webapp/test/mollifierClaimResolution.test.ts
  • apps/webapp/test/resolveBatchForRealtime.test.ts
  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
📚 Learning: 2026-07-18T14:01:28.319Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 4284
File: internal-packages/run-store/src/runOpsStore.idempotencyGlobalScopeCrossDb.test.ts:246-297
Timestamp: 2026-07-18T14:01:28.319Z
Learning: In the Trigger.dev run-ops split, when using parented child triggers with `global`-scope (or scope-absent) idempotency keys, ensure contention-safe deduping across different database residencies while the split is active. For these `global`/scope-absent keys, use the shared Redis idempotency claim keyed by (environment, task, hashed key) rather than relying on per-database unique constraints. By contrast, `run` and `attempt` scopes must include the run ID in their hash and therefore should not contend across different parents.

Applied to files:

  • apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts
📚 Learning: 2026-07-18T13:08:51.075Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 4284
File: apps/webapp/test/cancelRouteReplicaLag.guard.test.ts:50-123
Timestamp: 2026-07-18T13:08:51.075Z
Learning: In replica-lag/guard regression tests (e.g., cancelRouteReplicaLag.guard.test.ts), it is acceptable to stub “fix-orthogonal” collaborators (auth/session gates, downstream engine work, buffer state, redirect formatting, presigning, realtime instances, etc.) as long as the test still invokes the route’s real exported handler and continues to exercise the real run-store routing plus the replica-read/primary-fallback decision under the guard. Do not flag these targeted stubs just because the test uses real Postgres Testcontainers.

Applied to files:

  • apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts
🔇 Additional comments (8)
apps/webapp/test/claimTtl.test.ts (1)

1-55: LGTM!

apps/webapp/test/publishClaimResult.test.ts (1)

1-33: LGTM!

apps/webapp/test/reacquireClearedGlobalWinner.test.ts (1)

1-44: LGTM!

apps/webapp/test/mollifierClaimResolution.test.ts (1)

159-184: LGTM!

apps/webapp/test/idempotencyExpiredRecreateReserialize.test.ts (1)

1-93: LGTM!

apps/webapp/test/resolveBatchForRealtime.test.ts (1)

1-48: LGTM!

apps/webapp/test/routesBatchGetReplicaLag.guard.test.ts (2)

1-113: Otherwise a solid guard test.

Orthogonal-dependency stubbing (auth, idempotency cache, realtime stream) alongside the real route callers, real RoutingRunStore, and real frozen laggingReplica matches the established replica-lag guard-test convention.

Also applies to: 209-400


116-116: 🎯 Functional Correctness

No issue: batch_${CUID_25} is isolated per test. heteroRunOpsPostgresTest gives each case fresh cloned databases, so reusing the same batch ID across the five cases doesn’t collide.

			> Likely an incorrect or invalid review comment.

Walkthrough

Adds extensive Vitest integration coverage for replica-lag behavior across webapp routes, presenters, realtime services, run-engine operations, and run-store views. Tests simulate missing or stale replica rows, verify primary read fallbacks and primary-only mutation reads, validate tolerated null/empty responses, and cover cross-database global idempotency and waitpoint ownership behavior.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description misses the required Closes line and most template sections, so it does not meet the repository format. Add the Closes # line, checklist, testing steps, changelog, and screenshots sections to match the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 37.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main test-only replica-lag and idempotency guard changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/wait-until-idempotency-retry-rewait-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Dead since #4272 inlined per-write residency routing at each call site — the
private helper had zero callers (only doc-comments referenced it by name),
tripping eslint no-unused-private-class-members. Remove it and reword the
comments that referenced it.
@d-cs
d-cs force-pushed the fix/wait-until-idempotency-retry-rewait-tests branch from b9faca3 to 69fe2db Compare July 18, 2026 16:35
@d-cs
d-cs marked this pull request as ready for review July 18, 2026 16:39
@d-cs
d-cs force-pushed the fix/wait-until-idempotency-retry-rewait-tests branch 2 times, most recently from e7b4143 to 1abf430 Compare July 18, 2026 17:17
coderabbitai[bot]

This comment was marked as resolved.

@d-cs
d-cs force-pushed the fix/wait-until-idempotency-retry-rewait-tests branch 3 times, most recently from c0b053f to eac4192 Compare July 18, 2026 18:32
@d-cs

d-cs commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up on the shared-laggingReplica consolidation:

Rather than leave it for a later PR, I'm sweeping the remaining replica-lag test files in this PR that still carry a local lag-proxy onto the shared @internal/testcontainers laggingReplica primitive — the same change already applied to realtimeServicesReadView, replayReadAfterWrite, resolveRunForMutation, and routesSpanTraceReadView.

Scope: ~18 remaining files across run-store, run-engine, and webapp/test.

Exceptions that will stay on a local proxy (with an inline note), because the shared primitive intercepts only configured Prisma model reads and can't express them:

  • raw-query staling ($queryRaw/$executeRaw) — e.g. presentersWaitpointReadView, and any waitpoint-connection JOIN reads;
  • select-shape-scoped frozen scalar snapshots — e.g. runAttemptSystem;
  • any other proxy that returns fabricated non-null stale rows keyed on non-scalar where.

Each migrated file is verified green individually; pushing as an amend to the tests commit shortly.

@d-cs
d-cs force-pushed the fix/wait-until-idempotency-retry-rewait-tests branch from eac4192 to 47a6984 Compare July 18, 2026 18:46
@d-cs

d-cs commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

Shared-laggingReplica sweep complete (pushed in 47a6984c3).

Migrated onto the shared @internal/testcontainers laggingReplica primitive (20 files):

  • run-store (15): bulkActionReadView, cancelRunReadAfterWrite, dashboardAgentReadView, idempotencyResetReadView, modelRuntimeEnvReadView, realtimeServicesReadView, replayReadAfterWrite, resolveRunForMutationReplicaLag, routesSpanTraceReadView, sessionMetadataRouteReadView, sessionRunProbeReadAfterWrite, waitpointCompleteRouteAndReplayLoaderReadView, waitpointCompleteTokenReadAfterWrite, waitpointDedupReadAfterWrite, batchDependentAttemptReadView
  • webapp (5): idempotencyResetRouteReplicaLag.guard, metadataRouteReplicaLag.guard, replayRouteReplicaLag.guard, waitpointCallbackRouteReplicaLag.guard, waitpointCompleteRouteReplicaLag.guard

Each now calls laggingReplica(client, [{ model, mode: "missing" }]) (two configs where a site stales two models). All 20 verified green individually (run-store 22 tests, webapp 8 tests, plus the 14 from the first four).

Kept on a local proxy — the shared primitive can't express these (with reason):

  • presentersWaitpointReadView / waitpointPresenters.replicaLag.guard — stale $queryRaw/$queryRawUnsafe (the connected-runs JOIN); the shared primitive intercepts only Prisma models, never raw queries.
  • runAttemptSystemReplicaLag.guard — stales taskRun.findFirst for two exact select shapes with different frozen scalar snapshots; the shared primitive can't discriminate by select. (Already uses the shared primitive for its whole-row-missing cases.)

No change needed: concurrencySweeperCallbackReplicaLag.guard already used the shared primitive.

@d-cs
d-cs force-pushed the fix/wait-until-idempotency-retry-rewait-tests branch 2 times, most recently from 1a6af28 to 7102b75 Compare July 18, 2026 19:16
@d-cs

d-cs commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

coderabbitai[bot]

This comment was marked as resolved.

@d-cs
d-cs force-pushed the fix/wait-until-idempotency-retry-rewait-tests branch from 7102b75 to 551354a Compare July 18, 2026 19:52
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

The suite genuinely guards the regression — caller-driven RED-on-revert across every fix category, and the global-idempotency race is exercised for real over Redis + a two-DB store with a live SET NX barrier. A few non-blocking coverage notes:

  • The batch-404 test asserts the server stamps x-should-retry on the GET routes (correct), but nothing drives the subscribeToBatch shape path that ignores it — that's the gap behind the batches note on fix: read-your-writes + global-scope idempotency correctness under the run-ops split #4284.
  • The origin wait.until dedup is guarded at the store seam, not end-to-end through WaitpointSystem.
  • retryDecisionReadAfterWrite and the store-level *ReadView/*ReadAfterWrite files are characterizations that stay green on a caller-fix revert — the real revert guards for those sites are the caller-driven *.guard.test.ts files.

@d-cs
d-cs force-pushed the fix/wait-until-idempotency-retry-rewait branch from 2941d71 to 6ae9b8a Compare July 19, 2026 14:13
…me batch hardening

Production follow-ups from the CodeRabbit/Claude/Devin review. Caller-driven guard tests are in the
stacked tests PR; the two test edits here are coupled to the production change (the publishClaim
signature change invalidates a main assertion; the fan-out removal obsoletes a main test).

- Claim TTL floor (TRIGGER_MOLLIFIER_CLAIM_MIN_TTL_SECONDS, default 5) independent of the customer key
  TTL, so a short key TTL can't expire the claim mid-pipeline and let a loser re-claim.
- publishClaim returns the buffer CAS result; the trigger success path detects + logs a no-op'd publish.
- reacquireClearedGlobalWinner fails closed with a retryable 503 (exhaustion / unfindable winner); the
  expired/failed clear-and-recreate path routes through it too (Devin), so that recreate is serialised.
- Realtime batch route re-reads the owning primary on a replica miss (backend-agnostic; closes the
  Electric ShapeStream permanent-404 for self-hosters).
- Remove the classifiable-id cross-store fan-out from findRun: a run's id-shape fixes its residency for
  life, so the single-store read is correct and the fan-out was dead code.
@d-cs
d-cs force-pushed the fix/wait-until-idempotency-retry-rewait branch from 568b3bf to 1c625e1 Compare July 19, 2026 15:10
d-cs added 2 commits July 19, 2026 16:10
…tency guards

Guards for the production fixes in the base PR. Each fixed site has a
caller-driven test that drives the real exported route/presenter/service/engine
caller against a real Postgres with the owning replica frozen (shared
laggingReplica primitive), and goes RED when the fix is reverted; tolerated
read-view sites carry a caller-driven GREEN proof of self-healing. The
global-scope idempotency guard drives the real dedup + claim path through a real
MollifierBuffer over a Redis testcontainer, incl. the cross-DB andWait and the
expired/failed clear-and-recreate reacquire cases.
…w fixes

Caller-driven / unit guards for the production hardening in the base PR: claim-TTL floor,
publishClaim CAS result, reacquire fail-closed 503, expired/failed global-scope recreate
re-serialisation, and the realtime batch primary re-read. Split out of the base PR so the
production change stays reviewable on its own.
@d-cs
d-cs force-pushed the fix/wait-until-idempotency-retry-rewait-tests branch from 551354a to d1f1a85 Compare July 19, 2026 15:11

@devin-ai-integration devin-ai-integration 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.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

- cancelRun / idempotencyReset NEW-ksuid cases: use a run-ops-shaped friendlyId so the by-friendlyId
  read routes to NEW single-store (the fan-out that masked the fake LEGACY-classifying friendlyId is
  gone; a real NEW run's friendlyId classifies NEW).
- routesBatchGet realtime.v1.batches case: assert the loader now recovers a stale-on-replica batch via
  the owning-primary re-read (reaches streamBatch, 200) instead of a resource-gate 404.
d-cs added a commit that referenced this pull request Jul 19, 2026
…e run-ops split (#4284)

## What & why

Two related correctness fixes for the run-ops DB split. Under the split,
run-store reads can route to a **lagging read replica**; a just-written
run/waitpoint/batch can then be missed, causing a wrong decision.

**1. Read-your-writes → owning primary.** Surfaced first as an
intermittent `wait.until({ idempotencyKey })` re-wait on retry. Auditing
the run-store read surface found the same class at sibling sites (some
gating mutations or returning spurious 404s, others
tolerable/self-healing). Reads that must observe their own writes now
route to the owning **primary**
(`findRun`/`findWaitpoint`/`findBatchTaskRunByFriendlyId` →
`*OnPrimary`, a primary re-read on a miss, or a retryable 404 where the
SDK polls). Read-view reads stay on the replica. All additive — the
happy path is unchanged.

**2. Global-scope idempotency across the split.** A `global`-scope key
carries no per-run salt, so the same `(env, task, key)` triggered
concurrently from parents resident on **different** run-ops DBs could
dedup-miss on each DB and create a duplicate (the per-DB unique index
can't enforce cross-DB uniqueness). Such triggers (global scope, or
scope-absent, while split is active) are serialized through the existing
Redis idempotency claim, the loser resolves the winner by id across both
DBs, and the claim is reacquired on the expired/failed
clear-and-recreate path. `run`/`attempt` scope embed the run id and
never contend.

## Stacked for review

This is the **base** of a 2-PR stack, split so review is easier:
- **This PR** — production code only (34 files).
- **Stacked tests PR →
#4285 — the
caller-driven guards (55 test files) on top of this branch.

## Validation

Local run-ops split, **both 2-DB and 3-DB**, fresh boot on this branch:
SDK canary 64/71 (only the known concurrency/input-streams/s3 failures),
quarantine sweep **0 unexpected** (340 pass / 16 known / 4 local) in
each topology, dashboard e2e 0 failed. No product regressions.
Base automatically changed from fix/wait-until-idempotency-retry-rewait to main July 19, 2026 16:57
@d-cs
d-cs enabled auto-merge (squash) July 19, 2026 16:58
@d-cs
d-cs merged commit a7c734c into main Jul 19, 2026
31 of 32 checks passed
@d-cs
d-cs deleted the fix/wait-until-idempotency-retry-rewait-tests branch July 19, 2026 17:06
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