Skip to content

feat(run-ops): webapp routes — friendlyId reads, cross-seam token resolution, co-location writes#4123

Merged
d-cs merged 11 commits into
mainfrom
runops/pr09-routes
Jul 3, 2026
Merged

feat(run-ops): webapp routes — friendlyId reads, cross-seam token resolution, co-location writes#4123
d-cs merged 11 commits into
mainfrom
runops/pr09-routes

Conversation

@d-cs

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

Copy link
Copy Markdown
Collaborator

What

Wires the run-ops split into the webapp routes so requests are routed correctly across the store boundary. Builds on the presenter rework from the previous PR; this layer is the route/loader/action glue that calls those presenters and the run store.

  • Route loaders and actions that previously looked runs up by internal id now resolve by friendlyId through the run store, so a run is found regardless of which store holds it. This spans the dashboard run routes, the resource routes (resources.runs.*, resources.taskruns.* cancel/debug/replay, logs download), the API batch/run routes (api.v1/api.v2/api.v3), realtime routes, sync-traces, and the admin debugRun helper.
  • Public wait-token routes are updated to resolve tokens across the split boundary: the waitpoint-token complete / callback / retrieve / wait routes look the token up through the store, and the resolver re-checks auth after resolution rather than relying on a co-located join. Waitpoint writes go to the co-location store that owns them.
  • Waitpoint-token handling is moved onto an always-cuid contract, and the token tests are rewritten against that contract.

New tests cover token resolution across the seam (waitpointTokenResolve), the cross-seam guard on the complete route, the control-plane callback path, and the api.v1.waitpoints.tokens surface.

Why

Part of the run-ops database split. This is PR9 of the series (PR8/9/10): it connects the split-aware read presenters (PR8) to the actual HTTP/loader surface so reads and token resolution route correctly, without yet enabling the split. The behaviour-changing activation stays isolated in PR10 on top of this.

Tests

pnpm run test --filter webapp for the affected route/resolver suites, including the new cross-seam-guard and waitpoint-token suites.

Notes

Draft, stacked on #4122 (runops/pr08-presenters). Review that first; this diff is against it.

Server-change / changeset note to be added at stack-assembly time.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: daf148a

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 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This change migrates numerous webapp routes and admin tooling from directly querying Prisma relations (run.runtimeEnvironment, run.project, waitpoint.environment) to resolving environments via a new controlPlaneResolver and runStore abstraction. Waitpoint lookups now use a resolveWaitpointThroughReadThrough helper that fans out across run-ops and control-plane replica databases. Batch task run lookups migrate to runStore methods. Presenters (waitpoint, batch list/detail) are wired with explicit run-ops replica clients and split-read flags. Extensive new tests validate cross-seam waitpoint residency, completion guard ordering, store selection, and HTTP-callback resolution across heterogeneous Postgres databases. A changelog entry documents the routing change.

Changes

Area Change
Run redirect/impersonation routes Narrow findRun selections, add explicit authorization checks and controlPlaneResolver.resolveAuthenticatedEnv calls
Run detail/debug/cancel/replay/download routes Replace embedded Prisma joins with separate authorization plus resolved environment/locked-worker context
Realtime routes Resolve environment via control plane instead of nested runtimeEnvironment relations
Waitpoint completion/callback routes Use resolveWaitpointThroughReadThrough for cross-store resolution; createDateTimeWaitpoint now passes runId
Batch routes Migrate lookups from $replica queries to runStore methods; idempotency checks enforce environment match
Presenters Construct with explicit runOpsNewReplicaClient/runOpsLegacyReplica/splitEnabled dependencies
Admin debug tool Use resolved environment for Redis/MarQS key generation
Tests New suites for cross-seam waitpoint residency, completion guards, store selection, and callback resolution
Docs New changelog entry describing run-ops split route read routing

Sequence Diagram(s)

sequenceDiagram
  participant Route as Route Loader/Action
  participant RunStore as runStore
  participant Replica as $replica (authorization)
  participant Resolver as controlPlaneResolver
  Route->>RunStore: findRun(friendlyId, select ids)
  Route->>Replica: project.findFirst (membership check)
  Replica-->>Route: authorized project or null
  Route->>Resolver: resolveAuthenticatedEnv(runtimeEnvironmentId)
  Resolver-->>Route: resolved environment or null
  Route-->>Route: build redirect/response using resolved environment
Loading
sequenceDiagram
  participant Route as Waitpoint Route
  participant ReadThrough as resolveWaitpointThroughReadThrough
  participant RunOps as run-ops replica
  participant ControlPlane as control-plane replica
  participant Resolver as controlPlaneResolver
  Route->>ReadThrough: resolve(waitpointId, environmentId)
  ReadThrough->>RunOps: waitpoint.findFirst
  alt not found in run-ops
    ReadThrough->>ControlPlane: waitpoint.findFirst
  end
  ReadThrough-->>Route: waitpoint record
  Route->>Resolver: resolveAuthenticatedEnv(waitpoint.environmentId)
  Resolver-->>Route: environment (with parentEnvironment apiKey fallback)
  Route-->>Route: verify hash / complete waitpoint
Loading

Related PRs: None identified.

Suggested labels: area: webapp, area: runengine

Suggested reviewers: ericallam, matt-aitken

🥕 A carrot in the codebase, resolved through the plane,

No longer hopping down the relation lane.

Read-through routes across two stores it goes,

Where waitpoints live, now only the resolver knows.

Tests dig deep in PG14 and PG17's soil.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers intent and testing, but it does not follow the required template and omits Closes #issue, checklist, and screenshots sections. Rewrite the PR description to match the template: add Closes #issue, complete the checklist, keep Testing/Changelog, and include Screenshots.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title is concise and accurately summarizes the main change: run-ops split-aware reads and waitpoint token handling.
✨ 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 runops/pr09-routes

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.

devin-ai-integration[bot]

This comment was marked as resolved.

@d-cs d-cs force-pushed the runops/pr08-presenters branch from 9b6eee8 to 94eaef6 Compare July 2, 2026 18:02
@d-cs d-cs force-pushed the runops/pr09-routes branch from 4ba9267 to ec1aa6e Compare July 2, 2026 18:03
@pkg-pr-new

pkg-pr-new Bot commented Jul 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

@trigger.dev/build

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

trigger.dev

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

@trigger.dev/core

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

@trigger.dev/python

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

@trigger.dev/react-hooks

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

@trigger.dev/redis-worker

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

@trigger.dev/rsc

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

@trigger.dev/schema-to-json

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

@trigger.dev/sdk

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

commit: 56ec38c

coderabbitai[bot]

This comment was marked as resolved.

@d-cs

d-cs commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

On the three maintainability nitpicks in the review summary:

  • The as unknown as PrismaClientOrTransaction double-casts on the injected run-ops replica clients are intentional and consistent across the waitpoint presenter wiring. The run-ops replica client types are structurally compatible at the call sites we use; aligning the exported client types to drop the cast is a broader typing change outside this PR's read-routing scope. Kept as-is for consistency (the newly-wired waitpoint detail route follows the same pattern).
  • The tag-write test asserting the write lands on the control-plane client: noted; the production tag path is covered by the route-level tests, and rewiring this assertion through the model function pulls in unrelated route deps for no additional coverage of the split behaviour under test.

No code changes for these; the security/correctness comments have been addressed in the latest commit.

@d-cs d-cs force-pushed the runops/pr08-presenters branch from 6e4c0de to 48c395b Compare July 2, 2026 19:25
@d-cs d-cs force-pushed the runops/pr09-routes branch from 30ccb91 to caebbbc Compare July 2, 2026 19:25
@d-cs d-cs force-pushed the runops/pr08-presenters branch from f8b64bd to e145839 Compare July 2, 2026 20:23
@d-cs d-cs force-pushed the runops/pr09-routes branch from e2cdc6d to 00f588c Compare July 2, 2026 20:23
@d-cs d-cs force-pushed the runops/pr08-presenters branch from e145839 to 6cd0085 Compare July 2, 2026 20:38
@d-cs d-cs force-pushed the runops/pr09-routes branch from 00f588c to 077917b Compare July 2, 2026 20:38
@d-cs d-cs force-pushed the runops/pr08-presenters branch from 6cd0085 to 5ccf63f Compare July 2, 2026 21:44
@d-cs d-cs force-pushed the runops/pr09-routes branch from 077917b to b74993f Compare July 2, 2026 21:44
@d-cs d-cs force-pushed the runops/pr08-presenters branch from 5ccf63f to 105ba1c Compare July 3, 2026 00:17
@d-cs d-cs force-pushed the runops/pr09-routes branch 2 times, most recently from e7056ff to fa76c1b Compare July 3, 2026 01:32
@d-cs d-cs force-pushed the runops/pr08-presenters branch from f7cf260 to d31f2e9 Compare July 3, 2026 08:51
@d-cs d-cs force-pushed the runops/pr09-routes branch from 3bee9ab to be4e08a Compare July 3, 2026 08:51
@d-cs d-cs force-pushed the runops/pr08-presenters branch from d31f2e9 to 4bfd808 Compare July 3, 2026 10:02
@d-cs d-cs force-pushed the runops/pr09-routes branch from be4e08a to d9ee04f Compare July 3, 2026 10:02
@d-cs d-cs force-pushed the runops/pr08-presenters branch from 4bfd808 to 9197016 Compare July 3, 2026 10:37
@d-cs d-cs force-pushed the runops/pr09-routes branch from d9ee04f to 495be7c Compare July 3, 2026 10:37
@d-cs d-cs force-pushed the runops/pr08-presenters branch from 9197016 to fa0d473 Compare July 3, 2026 10:44
@d-cs d-cs force-pushed the runops/pr09-routes branch from 495be7c to 0c5b193 Compare July 3, 2026 10:44
@d-cs d-cs force-pushed the runops/pr08-presenters branch from fa0d473 to 50463ce Compare July 3, 2026 11:08
@d-cs d-cs force-pushed the runops/pr08-presenters branch from 15d6987 to 4862283 Compare July 3, 2026 16:44
@d-cs d-cs force-pushed the runops/pr09-routes branch from f47c32d to dd883b6 Compare July 3, 2026 16:44
@d-cs d-cs force-pushed the runops/pr08-presenters branch from 4862283 to abb6c01 Compare July 3, 2026 17:08
@d-cs d-cs force-pushed the runops/pr09-routes branch from dd883b6 to 56ec38c Compare July 3, 2026 17:08
@d-cs d-cs force-pushed the runops/pr08-presenters branch from abb6c01 to a4deee1 Compare July 3, 2026 17:53
@d-cs d-cs force-pushed the runops/pr09-routes branch from 56ec38c to dc8dd5a Compare July 3, 2026 17:53
@d-cs d-cs force-pushed the runops/pr08-presenters branch from a4deee1 to 7ed8483 Compare July 3, 2026 18:06
@d-cs d-cs force-pushed the runops/pr09-routes branch from dc8dd5a to 5888fdd Compare July 3, 2026 18:07
@d-cs d-cs force-pushed the runops/pr08-presenters branch from 7ed8483 to 77236dd Compare July 3, 2026 19:03
@d-cs d-cs force-pushed the runops/pr09-routes branch 2 times, most recently from d36e335 to 4713776 Compare July 3, 2026 19:12
Base automatically changed from runops/pr08-presenters to main July 3, 2026 19:38
d-cs and others added 11 commits July 3, 2026 20:38
…ck, co-location writes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… contract

Waitpoint ids are always cuid; residency is decided by co-location routing,
not id-shape. Remove the removed setKsuidMintEnabled global and flip the
standalone-token assertions to cuid/LEGACY. Drive the NEW/cross-seam side from
a ksuid run co-locating its (cuid) token on #new instead of a ksuid token, and
keep the cross-seam guard consulted unconditionally on a plain cuid token.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The public wait-token routes (complete, HTTP callback, retrieve) resolved the
waitpoint with a bare read-through that defaulted its new-side client to the
dedicated run-ops replica and gated on the async mint flag. A standalone wait
token has a cuid id and, having no owning run, is written to the control-plane
store, so under the split topology the run-ops replica does not hold it and the
routes returned 404 "Waitpoint not found".

Resolve these routes the same way the working waiter route does: fan out through
the run-ops replica first, then the control-plane replica, so both a co-located
(run-owned) waitpoint and a standalone control-plane token are found. Gate the
fan-out on the URL-presence read gate rather than the mint flag, so read
visibility spans both DBs whenever both are configured — including the window
where both database URLs are set but the mint flag is off. The retrieve route
hands the same fan-out clients and gate to ApiWaitpointPresenter.

Adds a two-database testcontainer regression proving a control-plane-resident
standalone token resolves via the legacy fallback under the read gate, and that
the passthrough branch (gate off) misses it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment/label/title cleanup only — no product logic or test behavior
changed. Removes leftover enumeration scaffolding across the PR10 files:
Test A-D and Leg 1-4 case markers, (A)/(B)/(D) parenthesized markers,
Step 1-3 prefixes, and --- comment framing, while preserving the
behavioral prose and the describe/it titles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lit deps for waitpoint detail

Route the org-membership authorization gate through the primary client on the
debug, run-inspector, and idempotency-key-reset routes so it matches the cancel
and replay paths and never authorizes against lagging replica state. Restore the
primary-DB org fallback in the replay action's RBAC scope resolver so the scope
is never resolved without an org during replica lag. Pass the full split-read
deps to WaitpointPresenter on the detail route so a waitpoint resident on the new
run-ops DB resolves the same way it does on the list route.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… test's db.server mock

The callback route now resolves the waitpoint via resolveWaitpointThroughReadThrough,
whose module reads runOpsSplitReadEnabled off ~/db.server at import. The test's vi.mock
omitted that export, so the mocked module threw at import and the whole suite failed to
collect. Provide it (split-on) to match the read path the route exercises; ksuid (NEW)
waitpoint ids route to the runOpsNewReplica proxy pointing at the seeded container.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-token test

The "control-plane-resident standalone token is found when the run-ops
replica does not hold it" case relied on the ambient RUN_OPS_SPLIT_ENABLED
env being on. Locally .env sets it (plus distinct TASK_RUN_* URLs) so the
resolver fans out new->legacy and finds the cuid-shaped token. In CI those
vars are unset, so resolveWaitpointThroughReadThrough falls back to
isSplitEnabled() -> false -> single-DB passthrough, which reads only the
run-ops replica (prisma17), never the control-plane legacy replica (prisma14)
that holds the token. The read returned null and the assertion
"expected null not to be null" failed on CI shard 3.

Inject deps.splitEnabled: true (mirroring the sibling read-gate test) so the
fan-out is exercised deterministically regardless of ambient env. Test-only;
preserves exactly what the case verifies (legacy fallback finds a
control-plane token the new replica lacks).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…deps

The api.v1.batches.$batchParam.results route constructed ApiBatchResultsPresenter
with no read-through deps, collapsing call() to a passthrough read off the
control-plane replica only. A NEW-resident (ksuid) batch on the dedicated run-ops
DB was invisible to that read and the route returned 404. Wire the presenter with
splitEnabled + newClient + legacyReplica, mirroring the batch-list and waitpoint
token read routes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@d-cs d-cs force-pushed the runops/pr09-routes branch from 4713776 to daf148a Compare July 3, 2026 19:39
@d-cs d-cs marked this pull request as ready for review July 3, 2026 19:39

@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 found 1 new potential issue.

Open in Devin Review

@d-cs d-cs merged commit 618b921 into main Jul 3, 2026
37 checks passed
@d-cs d-cs deleted the runops/pr09-routes branch July 3, 2026 20:10
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