Skip to content

fix: scope the id-addressed storage APIs by namespace - #606

Merged
kartikeya-27 merged 5 commits into
masterfrom
fix/namespace-scoping-id-apis
Aug 2, 2026
Merged

fix: scope the id-addressed storage APIs by namespace#606
kartikeya-27 merged 5 commits into
masterfrom
fix/namespace-scoping-id-apis

Conversation

@pratyush618

@pratyush618 pratyush618 commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #605 — review that one first. Base retargets to master once it merges.

Summary

  • A caller scoped to one namespace could still read or mutate another namespace's job by holding its id: get_job, the cancel paths, progress, logs, errors and the archive listing all took a bare id.
  • The recovery and scheduling paths had the same hole from the other direction: reap_stale_jobs, reap_orphaned_jobs and count_running_by_task swept the whole cluster, so a scheduler could time out or retry another namespace's job and then record the outcome under its own.
  • Closes Complete namespace scoping across the id-addressed storage APIs #597. Also closes Dashboard views ignore the configured namespace #587 — see below.

On #587

It needed no code. #596 had already scoped stats, list_dead*, get_metrics and query_task_logs, and tests/namespace_scoping.rs already covered them. The one listing left unscoped was list_archived, which #597 enumerates — it lands here, together with the docs note #587 asked for.

Changes

Storage (traits.rs + diesel_common + Redis + the delegate! macro) — fourteen methods take namespace: Option<&str>:

get_job · request_cancel · is_cancel_requested · mark_cancelled · update_progress · cascade_cancel · get_job_errors · get_task_logs · get_task_logs_after · list_archived · 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 — so a caller scoped to one tenant learns nothing about ids outside it. None still addresses every namespace, so a single-tenant deployment is unaffected.

Callers — the scheduler forwards its own namespace from maintenance.rs and poller.rs; every binding (Python, Node, Java), taskito-server and the TUI forward the queue's.

Two paths needed the namespace threaded in, not just forwarded. CancelSignals holds storage rather than a queue, so it now carries the worker's namespace. The executor side-channel's progress queue carried only (job_id, progress) — the dispatch already knew the namespace, so it rides along now, exactly as the log queue already did.

Cross-namespace dependencies. Dependency validation does not require a dependency to share a namespace with its dependent, so cascade_cancel filters rather than assuming. Not rejecting them at enqueue: that is a behaviour change past this issue's done-when, and the filter is correct either way.

Two methods have no namespace of their ownjob_errors (no column) and the Redis error list — so their scope comes from the job the rows belong to. Resolved before the connection is taken; a single-connection pool would deadlock on the second.

Docsshared/guides/operations/deployment.mdx gains a "Namespaces as a tenancy boundary" section stating what the boundary now covers, that an unset namespace addresses every namespace, and that a namespace confines a correctly-configured process rather than authenticating one.

Deferred, by decision

Workflow-run scoping. #597 lists paths that "load a run or node by an unscoped id". The cancel_job calls in those paths already pass the queue namespace, so the job side is closed here. The run side is not: workflow_runs has no namespace column at all, so scoping it needs a schema migration plus a signature change across the workflow storage trait and its three backends. That is its own change, not a rider on this one. Recorded as a known limit in the deployment guide.

Test plan

  • crates/taskito-core/tests/namespace_scoping.rs — 18 tests: every read reports not-found across the boundary, every mutation has no effect, and a scheduler in A never reaps or times out a job in B while an unscoped sweep still covers the cluster
  • One end-to-end test per SDK (tests/core/test_namespace.py, test/core/namespaceScoping.test.ts, NamespaceScopingTest.java)
  • cargo test --workspace green; clippy --all-targets --all-features and fmt clean; default / postgres / redis all check
  • Python 1393 passed / 14 skipped; Node 671 passed; Java ./gradlew build green including strict javadoc
  • Docs typecheck, lint and build clean
  • Mutation-checked: removing the cascade_cancel namespace filter fails a_cascade_cancel_stops_at_the_namespace_boundary

Note for reviewers

cargo check --workspace --all-targets does not compile #[cfg(feature = "redis")] test modules. The pre-commit clippy hook runs --all-features and found 23 further call sites after the workspace check was already clean.

Summary by CodeRabbit

  • New Features

    • Added namespace-aware isolation for job access, cancellation, progress, logs, errors, archival listings, recovery, and concurrency reporting.
    • Prevented cross-namespace operations while preserving unrestricted access when no namespace is configured.
    • Applied namespace scoping across Java, Node.js, Python, server, and dashboard integrations.
  • Documentation

    • Documented namespace tenancy boundaries and deployment guidance.
  • Tests

    • Added namespace-isolation coverage across SDKs and storage backends.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 83eb3cf7-a9d6-4386-ada6-f2b71981f2c9

📥 Commits

Reviewing files that changed from the base of the PR and between 7a44fd9 and ad87244.

📒 Files selected for processing (7)
  • crates/taskito-core/tests/namespace_scoping.rs
  • crates/taskito-java/src/queue/inspect.rs
  • crates/taskito-java/src/workflows/mod.rs
  • crates/taskito-node/src/queue/inspect.rs
  • crates/taskito-node/src/queue/workflows.rs
  • crates/taskito-python/src/py_queue/workflow_ops/nodes.rs
  • crates/taskito-server/src/dashboard/routes/jobs.rs
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • ByteVeda/taskito (manual)
🚧 Files skipped from review as they are similar to previous changes (7)
  • crates/taskito-java/src/queue/inspect.rs
  • crates/taskito-node/src/queue/workflows.rs
  • crates/taskito-java/src/workflows/mod.rs
  • crates/taskito-python/src/py_queue/workflow_ops/nodes.rs
  • crates/taskito-node/src/queue/inspect.rs
  • crates/taskito-server/src/dashboard/routes/jobs.rs
  • crates/taskito-core/tests/namespace_scoping.rs

📝 Walkthrough

Walkthrough

Optional namespace context now flows through storage contracts, Diesel and Redis backends, schedulers, workers, SDK bindings, dashboard routes, and tests. Reads, mutations, recovery, logs, errors, archival listings, and concurrency counts now support namespace filtering.

Changes

Namespace scoping

Layer / File(s) Summary
Storage contracts and delegation
crates/taskito-core/src/storage/{traits.rs,mod.rs}
Storage APIs and backend forwarding now accept optional namespaces.
Backend enforcement
crates/taskito-core/src/storage/{diesel_common,redis_backend}/...
Diesel and Redis restrict reads, mutations, logs, errors, recovery, counts, archives, and cascade cancellation by namespace.
Scheduler and worker propagation
crates/taskito-core/src/{scheduler,worker}/...
Schedulers and workers pass namespace context to maintenance, cancellation, running-count, result, and progress operations.
SDK and dashboard integration
crates/taskito-{java,node,python,server,tui}/...
Queue, dispatcher, workflow, inspection, replay, log, and dashboard operations forward namespaces.
Validation and documentation
crates/taskito-core/tests/..., sdks/*/test*, docs/...
Tests cover namespace isolation and unscoped access. Deployment documentation describes namespace behavior and configuration.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Queue
  participant Scheduler
  participant Storage
  participant JobRecord
  Queue->>Scheduler: start with namespace
  Scheduler->>Storage: request operation with namespace
  Storage->>JobRecord: filter or mutate matching record
  JobRecord-->>Storage: scoped result
  Storage-->>Queue: result or not-found
Loading

Possibly related PRs

Suggested reviewers: kartikeya-27

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: namespace scoping for ID-addressed storage APIs.
Linked Issues check ✅ Passed The changes implement namespace-scoped reads, mutations, recovery, scheduling, archive listings, bindings, DAG filtering, and tests required by issues [#597] and [#587].
Out of Scope Changes check ✅ Passed The code, documentation, and tests all support namespace isolation objectives from issues [#597] and [#587].
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@pratyush618
pratyush618 force-pushed the fix/namespace-scoping-id-apis branch from d14eeaa to 197b84b Compare August 2, 2026 09:12
Base automatically changed from fix/settings-cas-sdks to master August 2, 2026 09:50
A caller scoped to one namespace could read or mutate another's job through a bare id. Every id-addressed method now takes the namespace and answers not-found on a mismatch.
@pratyush618
pratyush618 force-pushed the fix/namespace-scoping-id-apis branch from 197b84b to 7a44fd9 Compare August 2, 2026 09:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/taskito-core/src/storage/diesel_common/jobs.rs (1)

1085-1152: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Reject cross-namespace dependencies at enqueue time.

validate_dependency and Redis validate_dep_ids do not compare namespaces. A pending job in namespace B can therefore depend on a job in namespace A. When A is cancelled or dead, cascade_cancel skips B, while deps_satisfied keeps B blocked by the non-Complete dependency. Apply the namespace check to every enqueue path, including batch and unique enqueue. Documentation alone does not resolve this stuck-job state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/taskito-core/src/storage/diesel_common/jobs.rs` around lines 1085 -
1152, Update dependency validation in validate_dependency and Redis
validate_dep_ids to reject dependencies whose namespace differs from the
enqueued job’s namespace. Apply the same validation consistently across
standard, batch, and unique enqueue paths so cross-namespace pending
dependencies cannot be created; do not rely on cascade_cancel filtering or
documentation as a workaround.

Source: Learnings

🧹 Nitpick comments (1)
sdks/java/src/test/java/org/byteveda/taskito/core/NamespaceScopingTest.java (1)

29-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Dequeue the job before asserting requestCancel/cancel_running_job isolation.

In all three SDKs, the test job stays Pending because it is enqueued but never dequeued. requestCancel/cancel_running_job targets an executing job; on a Pending job it returns false in every namespace, including the owning one. The assertion passes whether or not namespace filtering is implemented for this API, so it gives no real signal. The Rust reference test a_running_job_from_another_namespace_cannot_be_cancel_requested in crates/taskito-core/tests/namespace_scoping.rs avoids this by dequeuing the job to Running first.

  • sdks/java/src/test/java/org/byteveda/taskito/core/NamespaceScopingTest.java#L29-L53: dequeue/claim the job (or otherwise move it to Running) in namespace ns-a before calling b.requestCancel(id) at line 45, so the assertion exercises cross-namespace protection on a live job.
  • sdks/node/test/core/namespaceScoping.test.ts#L15-L36: start a worker or otherwise dequeue the job in namespace ns-a before calling b.requestCancel(id) at line 29, so the assertion exercises cross-namespace protection on a live job.
  • sdks/python/tests/core/test_namespace.py#L121-L150: dequeue the job in namespace ns-a before calling q_b.cancel_running_job(job.id) at line 144, so the assertion exercises cross-namespace protection on a live job.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/java/src/test/java/org/byteveda/taskito/core/NamespaceScopingTest.java`
around lines 29 - 53, Namespace cancellation tests currently use Pending jobs,
so they do not verify cross-namespace protection for running jobs. In
sdks/java/src/test/java/org/byteveda/taskito/core/NamespaceScopingTest.java
lines 29-53, dequeue or claim the job in namespace ns-a before
b.requestCancel(id); make the equivalent change in
sdks/node/test/core/namespaceScoping.test.ts lines 15-36 before
b.requestCancel(id and sdks/python/tests/core/test_namespace.py lines 121-150
before q_b.cancel_running_job(job.id). Preserve the existing ownership and
cancellation assertions after the job is Running.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/taskito-core/src/scheduler/result_handler.rs`:
- Line 71: Update every result-path mutation in the result handler to pass
self.namespace into complete_execution, record_error, retry, complete, and
complete_batch, including paths that currently mutate before or without get_job.
Enforce the namespace constraint atomically within each corresponding storage
mutation so scoped callers cannot modify foreign jobs.

In `@crates/taskito-core/src/storage/traits.rs`:
- Around line 118-121: Define and document one cross-namespace policy for
dependency edges, then apply it consistently to single, unique, and batch
enqueue paths across Diesel and Redis. Update the Storage trait methods
get_dependencies and get_dependents, their implementations, and all
dashboard/Java/Node/Python callers to enforce namespace filtering or rejection
according to that policy. Add isolation tests covering enqueue and both
edge-read directions, including Redis batch enqueue and Diesel enqueue_batch.

In `@crates/taskito-java/src/queue/inspect.rs`:
- Around line 324-327: Filter DAG edges by namespace before exposing
relationships: in crates/taskito-java/src/queue/inspect.rs:324-327,
crates/taskito-node/src/queue/inspect.rs:382-398, and
crates/taskito-server/src/dashboard/routes/jobs.rs:208-222, scope each
dependency and dependent lookup with the current namespace and only add the edge
or queue the adjacent job after it is visible; in
crates/taskito-server/src/dashboard/routes/jobs.rs:123-127, retain and pass the
namespace into build_dag.

In `@crates/taskito-java/src/workflows/mod.rs`:
- Line 427: Scope all workflow job operations by passing
queue.namespace.as_deref() instead of None: update get_job at
crates/taskito-java/src/workflows/mod.rs:427-427 and :681-681, and
request_cancel at :539-539. Ensure namespace-scoped queues cannot access or
mutate jobs in another namespace.

In `@crates/taskito-node/src/queue/workflows.rs`:
- Line 205: Scope all workflow job bridge lookups to the queue namespace by
passing self.namespace.as_deref() to get_job at
crates/taskito-node/src/queue/workflows.rs lines 205, 365, and 741, so
cross-namespace jobs return not-found and cannot be read or mutated.

In `@crates/taskito-python/src/py_queue/workflow_ops/nodes.rs`:
- Line 54: Update the get_job call in mark_workflow_node_result to pass the
queue namespace via self.namespace.as_deref() instead of None, ensuring workflow
result updates and cascade mutations only access jobs within the caller’s
namespace.

---

Outside diff comments:
In `@crates/taskito-core/src/storage/diesel_common/jobs.rs`:
- Around line 1085-1152: Update dependency validation in validate_dependency and
Redis validate_dep_ids to reject dependencies whose namespace differs from the
enqueued job’s namespace. Apply the same validation consistently across
standard, batch, and unique enqueue paths so cross-namespace pending
dependencies cannot be created; do not rely on cascade_cancel filtering or
documentation as a workaround.

---

Nitpick comments:
In `@sdks/java/src/test/java/org/byteveda/taskito/core/NamespaceScopingTest.java`:
- Around line 29-53: Namespace cancellation tests currently use Pending jobs, so
they do not verify cross-namespace protection for running jobs. In
sdks/java/src/test/java/org/byteveda/taskito/core/NamespaceScopingTest.java
lines 29-53, dequeue or claim the job in namespace ns-a before
b.requestCancel(id); make the equivalent change in
sdks/node/test/core/namespaceScoping.test.ts lines 15-36 before
b.requestCancel(id and sdks/python/tests/core/test_namespace.py lines 121-150
before q_b.cancel_running_job(job.id). Preserve the existing ownership and
cancellation assertions after the job is Running.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 74d6be1c-4eb7-4c6a-a32d-f0ba3d6f615b

📥 Commits

Reviewing files that changed from the base of the PR and between eb63304 and 7a44fd9.

📒 Files selected for processing (54)
  • crates/taskito-core/examples/hello.rs
  • crates/taskito-core/src/scheduler/maintenance.rs
  • crates/taskito-core/src/scheduler/mod.rs
  • crates/taskito-core/src/scheduler/poller.rs
  • crates/taskito-core/src/scheduler/result_handler.rs
  • crates/taskito-core/src/storage/diesel_common/archival.rs
  • crates/taskito-core/src/storage/diesel_common/dead_letter.rs
  • crates/taskito-core/src/storage/diesel_common/jobs.rs
  • crates/taskito-core/src/storage/diesel_common/logs.rs
  • crates/taskito-core/src/storage/mod.rs
  • crates/taskito-core/src/storage/redis_backend/archival.rs
  • crates/taskito-core/src/storage/redis_backend/dead_letter.rs
  • crates/taskito-core/src/storage/redis_backend/jobs/errors.rs
  • crates/taskito-core/src/storage/redis_backend/jobs/maintenance.rs
  • crates/taskito-core/src/storage/redis_backend/jobs/query.rs
  • crates/taskito-core/src/storage/redis_backend/jobs/state.rs
  • crates/taskito-core/src/storage/redis_backend/logs.rs
  • crates/taskito-core/src/storage/sqlite/tests.rs
  • crates/taskito-core/src/storage/traits.rs
  • crates/taskito-core/src/worker/cancel.rs
  • crates/taskito-core/src/worker/remote.rs
  • crates/taskito-core/src/worker/side_channel.rs
  • crates/taskito-core/tests/namespace_scoping.rs
  • crates/taskito-core/tests/rust/remote_tests.rs
  • crates/taskito-core/tests/rust/storage_tests.rs
  • crates/taskito-core/tests/rust/worker_tests.rs
  • crates/taskito-java/src/dispatcher.rs
  • crates/taskito-java/src/ffi_c.rs
  • crates/taskito-java/src/queue/admin.rs
  • crates/taskito-java/src/queue/inspect.rs
  • crates/taskito-java/src/queue/logs.rs
  • crates/taskito-java/src/queue/mod.rs
  • crates/taskito-java/src/worker.rs
  • crates/taskito-java/src/workflows/mod.rs
  • crates/taskito-node/src/dispatcher.rs
  • crates/taskito-node/src/queue/admin.rs
  • crates/taskito-node/src/queue/inspect.rs
  • crates/taskito-node/src/queue/logs.rs
  • crates/taskito-node/src/queue/mod.rs
  • crates/taskito-node/src/queue/workflows.rs
  • crates/taskito-node/src/worker.rs
  • crates/taskito-python/src/py_queue/inspection.rs
  • crates/taskito-python/src/py_queue/mod.rs
  • crates/taskito-python/src/py_queue/workflow_ops/fan_out.rs
  • crates/taskito-python/src/py_queue/workflow_ops/lifecycle.rs
  • crates/taskito-python/src/py_queue/workflow_ops/mod.rs
  • crates/taskito-python/src/py_queue/workflow_ops/nodes.rs
  • crates/taskito-server/src/dashboard/routes/jobs.rs
  • crates/taskito-server/tests/attach_e2e.rs
  • crates/taskito-tui/src/source/db.rs
  • docs/content/docs/shared/guides/operations/deployment.mdx
  • sdks/java/src/test/java/org/byteveda/taskito/core/NamespaceScopingTest.java
  • sdks/node/test/core/namespaceScoping.test.ts
  • sdks/python/tests/core/test_namespace.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • ByteVeda/taskito (manual)

Comment thread crates/taskito-core/src/scheduler/result_handler.rs
Comment thread crates/taskito-core/src/storage/traits.rs
Comment thread crates/taskito-java/src/queue/inspect.rs
Comment thread crates/taskito-java/src/workflows/mod.rs Outdated
Comment thread crates/taskito-node/src/queue/workflows.rs Outdated
Comment thread crates/taskito-python/src/py_queue/workflow_ops/nodes.rs Outdated
These sites had the queue namespace in hand and passed None, so a scoped queue could resolve and act on a job in another namespace. Distinct from workflow-run scoping, which needs a schema migration.
The walks pushed an edge per dependency id before looking the adjacent job up, so scoping get_job skipped the foreign node but still shipped its id. An edge is now kept only once both endpoints resolved.
@kartikeya-27
kartikeya-27 merged commit ea93827 into master Aug 2, 2026
34 checks passed
@kartikeya-27
kartikeya-27 deleted the fix/namespace-scoping-id-apis branch August 2, 2026 11:21
@pratyush618 pratyush618 mentioned this pull request Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Complete namespace scoping across the id-addressed storage APIs Dashboard views ignore the configured namespace

2 participants