Skip to content

refactor(run-engine): extract a WaitpointCoordinator seam around the Postgres waitpoint implementation - #4753

Draft
d-cs wants to merge 8 commits into
mainfrom
refactor/waitpoint-coordinator-seam-tri-13373
Draft

refactor(run-engine): extract a WaitpointCoordinator seam around the Postgres waitpoint implementation#4753
d-cs wants to merge 8 commits into
mainfrom
refactor/waitpoint-coordinator-seam-tri-13373

Conversation

@d-cs

@d-cs d-cs commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Extracts every Postgres waitpoint and edge operation out of WaitpointSystem into a WaitpointCoordinator seam with one Postgres implementation, so a different coordination backend can be plugged in later without any caller changing.

Pure refactor. Zero behaviour change, and zero test-file diffs — the existing engine corpus is the characterisation test.

What moved

WaitpointCoordinator (waitpointCoordinator/types.ts, declared with type) has nine members: clearRunBlockState, readRunBlockState, registerBlocks, registerBlocksLockless, complete, createDateTimeWaitpoint, createManualWaitpoint, mintAssociatedWaitpointData, createAssociatedWaitpoint.

LegacyPostgresWaitpointCoordinator implements them against the run-ops store. Its dependencies are { runStore, prisma, logger } only, so it structurally cannot reach the run lock, the worker, or the event bus — orchestration stays in WaitpointSystem, which keeps all ten public signatures, all six worker.enqueue sites, the racepoints, the snapshot transitions, and the event emissions.

Two register methods rather than one with a flag, so "the batch path issues no extra query" is structural instead of conditional. Both share one private edge-write helper.

Six notes for reviewers — please read before "simplifying" any of these

  1. nanoid(24) is called twice with different values on purpose, in each create path: once for the upsert where key, once for create.data. Hoisting either to a shared constant makes the where-key match the create-key, turning a guaranteed-miss upsert into a possible update. In createManualWaitpoint both calls plus WaitpointId.generate() stay inside the retry loop so each attempt tries a fresh key.

  2. The two enqueue conditions are deliberately asymmetric. DATETIME enqueues finishWaitpoint unconditionally after a non-cached create, with availableAt: completedAfter. MANUAL enqueues only when timeout is set. That is existing behaviour, not an oversight. The coordinator returns a discriminated union on kind rather than a boolean so the enqueue is structurally unreachable on the cached path.

  3. One false clause was deleted from a moved comment. The old comment on the full-clear delete claimed the caller's tx is not forwarded. The code does forward it, and PostgresRunStore uses tx ?? this.prisma, so a single store joins the caller's transaction — only the routing store strips it. The rest of that comment is unchanged.

  4. The MANUAL timeout enqueue now sits outside the P2002 retry loop. Safe because the worker is Redis-backed and cannot raise Prisma.PrismaClientKnownRequestError, so the loop never retried on it. If a Postgres-backed enqueue is ever swapped in, that equivalence breaks silently.

  5. The coordinator caches runStore/prisma/logger at construction, where the old code read this.$.* per call. Equivalent only because nothing reassigns them: one assignment at engine/index.ts, and the resources object is a const that is never mutated.

  6. Two comments in other files are now stale and were left aloneengine/index.ts and completeWaitpointCrossSeamGuard.test.ts both describe routing as the first statement of waitpointSystem.completeWaitpoint. Both tests still pass, because that guard sits in index.ts before the delegation. Left untouched to keep this diff to three files.

Preserved verbatim

The unnest edge CTE rather than a Waitpoint join; the pending count as a separate statement after the edge write (READ COMMITTED needs its own snapshot); completion's findWaitpointOnPrimary re-read through the resolved handle while the blocked-run fan-out goes back through the router; the residency and colocate hints, with colocation objects built only in the Postgres arm and the count keeping its runId argument; ON CONFLICT DO NOTHING and the (taskRunId, waitpointId, batchIndex) multi-index edge semantics; the unread batchId select, which rides inside two logger.debug payloads.

internal-packages/run-store/ is untouched, so the CTE and the conflict semantics never moved.

Verification

Check Result
Engine corpus 61/61 files, 353 passed, 1 skipped, 0 failed (baseline: 352 passed, 1 failed)
Test-file diffs empty
run-engine typecheck tsc --noEmit -p tsconfig.build.json exits 0
webapp typecheck 146 errors on this branch, 146 identical errors at baseline — pre-existing, none added

The webapp typecheck does not pass. The failures are pre-existing (PrismaPg not assignable to never; missing @trigger.dev/rbac exports) and the sorted error lists are byte-identical to the merge base, so this branch adds none — but the criterion is genuinely unmet and needs a separate fix.

No changeset and no .server-changes note: internal refactor with no user-visible change.

Follow-ups this surfaced

  • The dominant RUN waitpoint is still created outside the seam — buildRunAssociatedWaitpoint now mints through the coordinator, but the row is inserted nested inside createRun/createFailedRun. That needs its own packet before a second backend lands, or the commonest waitpoint gets split across two of them.
  • clearRunBlockState overloads opposite outcomes on undefined versus []: undefined clears every edge, [] clears none. Both callers are correct today; worth splitting when the file is next touched.
  • A stray non-.sql entry in internal-packages/clickhouse/schema/ breaks every containerTest in the repo, because the testcontainers migration reader readFiles every readdir entry without filtering despite a comment claiming it filters. Hit this during setup; unrelated to this change and left for a separate fix.

@changeset-bot

changeset-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: b2d56a9

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 Aug 21, 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 Plus

Run ID: 8a23f894-814f-45e2-9e7e-b1f838fd892a

📥 Commits

Reviewing files that changed from the base of the PR and between c5c2ea9 and b2d56a9.

📒 Files selected for processing (3)
  • internal-packages/run-engine/src/engine/systems/waitpointSystem.ts
  • internal-packages/run-engine/src/engine/waitpointCoordinator/legacyPostgresCoordinator.ts
  • internal-packages/run-engine/src/engine/waitpointCoordinator/types.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal-packages/run-engine/src/engine/waitpointCoordinator/types.ts
  • internal-packages/run-engine/src/engine/systems/waitpointSystem.ts
  • internal-packages/run-engine/src/engine/waitpointCoordinator/legacyPostgresCoordinator.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout. (33)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (24, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (17, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (15, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (22, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (23, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (2, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (12, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (16, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (14, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (21, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (11, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (20, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (19, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (13, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (1, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (5, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (9, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (10, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (18, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (3, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (4, 24)
  • GitHub Check: fk-cascade-guard / fk-cascade-guard
  • GitHub Check: runops-guard / runops-guard
  • GitHub Check: typecheck / typecheck
  • GitHub Check: internal / 🧪 Unit Tests: Internal
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (2, 2)
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (1, 2)
  • GitHub Check: code-quality / code-quality
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Build and publish previews

Walkthrough

Added the WaitpointCoordinator contract and a PostgreSQL implementation for waitpoint persistence and state operations. WaitpointSystem now delegates block registration, state reads and clearing, completion, date-time creation, manual creation, and associated waitpoint creation to the coordinator. The coordinator handles store routing, idempotency, expiration rotation, completion rereads, blocked-run lookup, and manual creation retries.

Merge Risk: ⚪ Minimal · up to b2d56

This refactor preserves the existing waitpoint behavior and introduces no actionable merge-blocking risk; it is merge-ready after normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes extracting a WaitpointCoordinator seam around the PostgreSQL implementation.
Description check ✅ Passed The description explains the refactor, testing, preserved behavior, known baseline failures, and follow-ups; it omits the issue and checklist template sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 3 files.
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 refactor/waitpoint-coordinator-seam-tri-13373

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.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
internal-packages/run-engine/src/engine/systems/waitpointSystem.ts (1)

45-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider injecting the coordinator through WaitpointSystemOptions.

WaitpointSystem constructs LegacyPostgresWaitpointCoordinator directly. The seam therefore cannot be substituted by any caller, and tests cannot supply a fake coordinator. An optional option keeps the current default and completes the stated goal that a non-Postgres implementation can replace it without caller changes.

♻️ Proposed optional injection

Add the field to the options type:

 export type WaitpointSystemOptions = {
   resources: SystemResources;
   executionSnapshotSystem: ExecutionSnapshotSystem;
   enqueueSystem: EnqueueSystem;
+  coordinator?: WaitpointCoordinator;
 };

Then default it in the constructor:

-    this.coordinator = new LegacyPostgresWaitpointCoordinator({
-      runStore: this.$.runStore,
-      prisma: this.$.prisma,
-      logger: this.$.logger,
-    });
+    this.coordinator =
+      options.coordinator ??
+      new LegacyPostgresWaitpointCoordinator({
+        runStore: this.$.runStore,
+        prisma: this.$.prisma,
+        logger: this.$.logger,
+      });

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: da984ec7-a423-4d2f-a6b0-9207cd712ff9

📥 Commits

Reviewing files that changed from the base of the PR and between c5c2ea9 and 3367c51.

📒 Files selected for processing (3)
  • internal-packages/run-engine/src/engine/systems/waitpointSystem.ts
  • internal-packages/run-engine/src/engine/waitpointCoordinator/legacyPostgresCoordinator.ts
  • internal-packages/run-engine/src/engine/waitpointCoordinator/types.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📜 Review details
⏰ Context from checks skipped due to timeout. (32)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (14, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (17, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (9, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (24, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (22, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (21, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (1, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (15, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (4, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (5, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (18, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (20, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (19, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (16, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (2, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (11, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (23, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (3, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (13, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (10, 24)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (12, 24)
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (1, 2)
  • GitHub Check: runops-guard / runops-guard
  • GitHub Check: fk-cascade-guard / fk-cascade-guard
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp (2, 2)
  • GitHub Check: internal / 🧪 Unit Tests: Internal
  • GitHub Check: typecheck / typecheck
  • GitHub Check: code-quality / code-quality
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{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 imports. Only use dynamic import() when:

  • Circular dependencies cannot be resolved otherwise
  • Code splitting is genuinely needed for performance
  • The module must be loaded conditionally at runtime

Files:

  • internal-packages/run-engine/src/engine/waitpointCoordinator/types.ts
  • internal-packages/run-engine/src/engine/systems/waitpointSystem.ts
  • internal-packages/run-engine/src/engine/waitpointCoordinator/legacyPostgresCoordinator.ts
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Files:

  • internal-packages/run-engine/src/engine/waitpointCoordinator/types.ts
  • internal-packages/run-engine/src/engine/systems/waitpointSystem.ts
  • internal-packages/run-engine/src/engine/waitpointCoordinator/legacyPostgresCoordinator.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:

  • internal-packages/run-engine/src/engine/waitpointCoordinator/types.ts
  • internal-packages/run-engine/src/engine/systems/waitpointSystem.ts
  • internal-packages/run-engine/src/engine/waitpointCoordinator/legacyPostgresCoordinator.ts
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Add crumbs as you write code — not just when debugging. Mark lines with
// @Crumbs or wrap blocks in `// `#region` `@crumbs. They stay on the
branch throughout development and are stripped by agentcrumbs strip
before merge.
Do not invent new namespaces — pick from this table or ask first.
Do not flag // @Crumbs markers, `#region `@crumbs blocks, or agentcrumbs
imports in reviews. These are temporary debug instrumentation stripped
before merge. Data logged in crumbs (IDs, names, values) never reaches
production.

Files:

  • internal-packages/run-engine/src/engine/waitpointCoordinator/types.ts
  • internal-packages/run-engine/src/engine/systems/waitpointSystem.ts
  • internal-packages/run-engine/src/engine/waitpointCoordinator/legacyPostgresCoordinator.ts
internal-packages/run-engine/src/engine/systems/**/*.ts

📄 CodeRabbit inference engine (internal-packages/run-engine/CLAUDE.md)

Integrate OpenTelemetry tracer and meter instrumentation in RunEngine systems for observability

Files:

  • internal-packages/run-engine/src/engine/systems/waitpointSystem.ts
🧠 Learnings (1)
📚 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:

  • internal-packages/run-engine/src/engine/waitpointCoordinator/types.ts
🔇 Additional comments (23)
internal-packages/run-engine/src/engine/waitpointCoordinator/types.ts (6)

16-32: LGTM!


34-56: LGTM!


58-78: LGTM!


80-99: LGTM!


101-133: LGTM!


135-145: 🗄️ Data Integrity & Integration

Remove this concern

WaitpointId.generate() returns only id and friendlyId, which match AssociatedWaitpointData.

			> Likely an incorrect or invalid review comment.
internal-packages/run-engine/src/engine/waitpointCoordinator/legacyPostgresCoordinator.ts (9)

1-44: LGTM!


46-66: LGTM!


68-83: LGTM!


85-109: LGTM!


111-183: LGTM!


185-264: LGTM!


266-377: LGTM!


413-437: LGTM!


397-411: 🗄️ Data Integrity & Integration

No routing change is required. runStore.createWaitpoint uses data.completedByTaskRunId when no explicit routing hint is provided, so this waitpoint co-locates with its owning run.

			> Likely an incorrect or invalid review comment.
internal-packages/run-engine/src/engine/systems/waitpointSystem.ts (8)

1-13: LGTM!


65-67: LGTM!


83-95: LGTM!


152-172: LGTM!


200-228: LGTM!


285-296: LGTM!

Also applies to: 395-405


472-472: LGTM!

Also applies to: 703-706


728-728: LGTM!

Also applies to: 812-814

@d-cs d-cs self-assigned this Aug 21, 2026

@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

`BlockedRun` is only named inside types.ts, by CompleteResult. The repo's
knip gate rejects unused exports, so drop the export keyword rather than
add a knip.json exception — nothing outside this file needs the name yet.
@d-cs
d-cs force-pushed the refactor/waitpoint-coordinator-seam-tri-13373 branch from 3367c51 to b2d56a9 Compare August 21, 2026 15:18
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@pkg-pr-new

pkg-pr-new Bot commented Aug 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

@trigger.dev/build

npm i https://pkg.pr.new/@trigger.dev/build@b2d56a9

trigger.dev

npm i https://pkg.pr.new/trigger.dev@b2d56a9

@trigger.dev/core

npm i https://pkg.pr.new/@trigger.dev/core@b2d56a9

@trigger.dev/python

npm i https://pkg.pr.new/@trigger.dev/python@b2d56a9

@trigger.dev/react-hooks

npm i https://pkg.pr.new/@trigger.dev/react-hooks@b2d56a9

@trigger.dev/redis-worker

npm i https://pkg.pr.new/@trigger.dev/redis-worker@b2d56a9

@trigger.dev/rsc

npm i https://pkg.pr.new/@trigger.dev/rsc@b2d56a9

@trigger.dev/schema-to-json

npm i https://pkg.pr.new/@trigger.dev/schema-to-json@b2d56a9

@trigger.dev/sdk

npm i https://pkg.pr.new/@trigger.dev/sdk@b2d56a9

commit: b2d56a9

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.

1 participant