Uh oh!
There was an error while loading. Please reload this page.
feat(run-ops): webapp routes — friendlyId reads, cross-seam token resolution, co-location writes - #4123
Conversation
|
WalkthroughThis 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
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
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
Related PRs: None identified. Suggested labels: area: webapp, area: runengine Suggested reviewers: ericallam, matt-aitken 🥕 A carrot in the codebase, resolved through the plane, 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
@trigger.dev/buildtrigger.dev@trigger.dev/core@trigger.dev/python@trigger.dev/react-hooks@trigger.dev/redis-worker@trigger.dev/rsc@trigger.dev/schema-to-json@trigger.dev/sdkcommit: |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
d-cs
commented
Jul 2, 2026
On the three maintainability nitpicks in the review summary:
No code changes for these; the security/correctness comments have been addressed in the latest commit. |
e7056ff to
fa76c1bCompared36e335 to
4713776Compare…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>
Uh oh!
There was an error while loading. Please reload this page.
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.
friendlyIdthrough 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 admindebugRunhelper.New tests cover token resolution across the seam (
waitpointTokenResolve), the cross-seam guard on the complete route, the control-plane callback path, and theapi.v1.waitpoints.tokenssurface.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 webappfor 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