Repository navigation
Complete namespace scoping across the id-addressed storage APIs #597
Description
#596 scopes the dashboard listings by namespace, closing #587. Reviewing it, CodeRabbit pointed out that the id-addressed and recovery paths beside them are still unscoped — so a caller holding an id from another namespace can still read or mutate that record.
The listings were the reported hole and are fixed. These are the rest of the surface, filed together because they share one root cause and one shape of fix: the Storage trait method takes no namespace, so every one of them needs a signature change carried through traits.rs, the diesel_common macros, all three backends, the delegate! macro, and each binding that calls it. That is why they did not ride along with #596.
Still unscoped
Recovery and scheduling
scheduler/maintenance.rs—reap_stale_jobs(now)andreap_orphaned_jobs(&live, now)take no namespace, so a scheduler can retry or dead-letter another namespace's job, andhandle_resultthen records the metric under its own namespace.scheduler/poller.rs—count_running_by_taskcounts across namespaces, so a job elsewhere consumes this scheduler'smax_concurrentbudget and causes a wrong reschedule.
Id-addressed reads and mutations
storage/diesel_common/jobs.rs—cancel_jobchecks the namespace, then callscascade_cancel(failed_job_id, reason), which archives every pending dependent without one. Dependency validation does not require dependencies to share a namespace, so a cancel in one namespace can cancel a dependent in another.storage/diesel_common/logs.rs—get_task_logs/get_task_logs_aftertake a job id and no namespace.taskito-server's job-log route and the Node bindings both expose them.taskito-node/src/queue/mod.rs—get_job,request_cancel,update_progressstill take a bare id.taskito-node/src/queue/inspect.rs—list_archived/list_archived_aftercall unscoped storage APIs, so a namespaced queue lists every namespace's archive.
Workflow state
taskito-java/src/workflows/mod.rsandtaskito-node/src/queue/workflows.rs— several paths load a run or node by an unscoped id and then mark it skipped, failed or cancelled, or ignore thefalsethatcancel_jobreturns on a namespace mismatch. A caller with a valid identifier can mutate workflow state owned by another namespace.
Shape of the fix
For each: add namespace: Option<&str> to the storage method, apply it as an equality filter, and return the same not-found answer on a mismatch that cancel_job already gives — a caller scoped to one tenant learns nothing about ids outside it. Then forward the queue namespace from every binding.
Worth deciding once, up front: whether cross-namespace job dependencies are a supported contract. If they are not, enqueue should reject them, which makes the cascade_cancel fix a filter rather than a behaviour change.
Done when
A caller scoped to namespace A, holding a valid id from namespace B, gets not-found from every read and no effect from every mutation — asserted per path, plus a recovery test proving a scheduler in A never reaps, retries or dead-letters a job in B.
Context
Raised by CodeRabbit on #596; the individual review threads carry its per-path analysis.
Activity
pratyush618 commentedon Aug 2, 2026
Fixed in #606 (`ea93827a`).
Fourteen
Storagemethods now takenamespace: Option<&str>—get_job,request_cancel,is_cancel_requested,mark_cancelled,update_progress,cascade_cancel,get_job_errors,get_task_logs(_after),list_archived(_after),count_running_by_task,reap_stale_jobs,reap_orphaned_jobs. A mismatch gets the same answer an unknown id gets: missing on a read, no effect on a mutation.Nonestill addresses every namespace.The done-when holds: 19 tests in
namespace_scoping.rscover every read reporting not-found and every mutation having no effect, plus a scheduler in A never reaping or timing out a job in B.Two things from this issue landed beyond the storage signatures.
CancelSignalsand the executor side-channel now carry the namespace — neither had it, and both address jobs by id. Review also caught a disclosure the signature change alone did not close: the DAG walks pushed an edge per dependency id before looking the adjacent job up, so scopingget_jobskipped the foreign node but still shipped its id. All four walks now keep an edge only once both endpoints resolved.Not closed here, tracked in #608: cross-namespace dependency policy (
get_dependencies/get_dependents), the result-path mutations, and workflow-run scoping. The workflow job-bridge half — workflow code callingget_job/request_cancelwithout a namespace — was fixed here; it needed no schema change.