Problem
The canonical worker detail route is /app/workers/[id] — params: { id } only, no workspace (or user) in the URL (engine/apps/web/app/workers/[id]/page.tsx).
The workspace is resolved at request time from get_active_workspace_id() (a session/context var set from the active workspace cookie/header), NOT from the URL. And worker_id is not globally unique — it is scoped by workspace (workspace_id_for_worker(worker_id); every Supabase query does .eq("workspace_id", ...); the run recipe lookup is keyed by (worker_id, user_id, workspace_id)).
Consequences:
- Opening
/app/workers/{id} with a different workspace active resolves to the wrong workspace's worker or 404s.
- The same
worker_id can exist in multiple workspaces → the bare link is ambiguous.
- Links are not portable/shareable across sessions; they only work because the recipient's session happens to default to the right workspace.
This is the same defect class as the run-proxy silent-403 just fixed in #2075 (worker config resolved by worker_id without workspace scope). The routing layer has the same worker_id == globally-unique assumption.
Proper fix
Worker URLs must be workspace-deterministic. Options:
- Path-scoped:
/app/w/{workspace_id}/workers/{worker_id} (preferred), or
- Query-scoped:
/app/workers/{id}?ws={workspace_id}, or
- Globally-unique opaque worker ids.
Requirements: keep back-compat (old /app/workers/{id} must still resolve — look up the worker's workspace for the authenticated user, redirect + pin the active workspace; handle the not-a-member / ambiguous cases), update all internal navigation links to the scoped URL, and have the API/active-workspace resolution prefer the URL's workspace when present.
Repo note: dashboard UI lives in the engine (engine/apps/web), so the fix is an engine change here.
Problem
The canonical worker detail route is
/app/workers/[id]—params: { id }only, no workspace (or user) in the URL (engine/apps/web/app/workers/[id]/page.tsx).The workspace is resolved at request time from
get_active_workspace_id()(a session/context var set from the active workspace cookie/header), NOT from the URL. Andworker_idis not globally unique — it is scoped by workspace (workspace_id_for_worker(worker_id); every Supabase query does.eq("workspace_id", ...); the run recipe lookup is keyed by(worker_id, user_id, workspace_id)).Consequences:
/app/workers/{id}with a different workspace active resolves to the wrong workspace's worker or 404s.worker_idcan exist in multiple workspaces → the bare link is ambiguous.This is the same defect class as the run-proxy silent-403 just fixed in #2075 (worker config resolved by
worker_idwithout workspace scope). The routing layer has the sameworker_id == globally-uniqueassumption.Proper fix
Worker URLs must be workspace-deterministic. Options:
/app/w/{workspace_id}/workers/{worker_id}(preferred), or/app/workers/{id}?ws={workspace_id}, orRequirements: keep back-compat (old
/app/workers/{id}must still resolve — look up the worker's workspace for the authenticated user, redirect + pin the active workspace; handle the not-a-member / ambiguous cases), update all internal navigation links to the scoped URL, and have the API/active-workspace resolution prefer the URL's workspace when present.Repo note: dashboard UI lives in the engine (
engine/apps/web), so the fix is an engine change here.