Skip to content

Feat: Per-user inference routes with ownership scoping#21

Merged
pdettori merged 2 commits into
mvp-v2from
feat/per-user-inference-routes
Jun 23, 2026
Merged

Feat: Per-user inference routes with ownership scoping#21
pdettori merged 2 commits into
mvp-v2from
feat/per-user-inference-routes

Conversation

@pdettori

Copy link
Copy Markdown
Member

Summary

  • Allow non-admin users to configure their own inference routing (previously admin-only)
  • Routes are stored with per-user scoped names (inference.local/<owner-hash>) so users get isolated config
  • Admin-created global routes (inference.local) serve as fallbacks when no per-user route exists
  • Sandbox bundle resolution automatically resolves the sandbox owner's route

Changes

  • Relax set_cluster_inference role from admin to user
  • Add scoped_route_name helper that encodes ownership in the store name
  • Stamp openshell.ai/owner label on new routes via stamp_owner
  • Verify caller owns the referenced provider before allowing route creation
  • Verify route ownership before allowing updates
  • get_cluster_inference resolves per-user route first, falls back to global
  • get_inference_bundle looks up sandbox owner and resolves their routes

Test plan

  • Existing unit tests pass (updated to 8-arg signature with None, "" for backward compat)
  • CI build passes
  • Manual: user can openshell inference set --provider <own-provider> --model <model> --no-verify
  • Manual: user can openshell inference get and see their own route
  • Manual: admin inference set still creates global route
  • Manual: user without per-user route falls back to admin-set global

Closes kagenti/kagenti#1995

Assisted-By: Claude Code

@pdettori pdettori force-pushed the feat/per-user-inference-routes branch from cc0dbad to 95b262b Compare June 23, 2026 11:01
Allow non-admin users to set their own inference routes. Routes are
stored with owner-scoped names (inference.local/<subject-hash>) so
each user gets isolated config while admin-created global routes
serve as fallbacks.

Changes:
- Relax set_cluster_inference role from admin to user
- Scope route names by owner for non-admin callers
- Stamp ownership label on new routes
- Verify provider ownership before referencing in a route
- Verify route ownership before updating
- Resolve per-user routes first, fall back to global
- Sandbox bundle resolution looks up sandbox owner

Closes: kagenti/kagenti#1995

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
@pdettori pdettori force-pushed the feat/per-user-inference-routes branch from 95b262b to 8277d6f Compare June 23, 2026 11:06

@pdettori pdettori left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Self-review (cannot self-approve)

Clean implementation of per-user inference route scoping. The authorization model is sound: ownership checks gate provider references, route updates, and route creation. CAS writes prevent TOCTOU issues. The main gap is test coverage for the new ownership paths — all existing tests pass None, "" for principal/admin_role.

Areas reviewed: Rust (authorization, ownership, error handling)
CI: DCO ✓, Rust ✓, lint ✓
Verdict: Would APPROVE if this weren't my own PR.

))
}

/// Resolve a route with owner-scoping for sandbox bundle: try per-user first,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

nit: unreachable!() — consider returning Status::internal("unexpected principal variant") for defense-in-depth if authorize_inference_bundle invariants ever change. Not blocking.

store: &Store,
route_name: &str,
provider_name: &str,
model_id: &str,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

nit: #[allow(clippy::too_many_arguments)] — a UpsertRequest struct would clean up the 8-param signature. Fine as-is for now.

request: Request<SetClusterInferenceRequest>,
) -> Result<Response<SetClusterInferenceResponse>, Status> {
let principal = request
.extensions()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

suggestion: All existing tests pass None, "" for the new principal/admin_role params, leaving the ownership logic untested at the unit level. Consider adding tests for:

  • Non-admin creates a scoped route (name = base/<subject>)
  • Non-admin rejected when referencing another user's provider
  • Ownership check blocks route update by non-owner
  • resolve_get_route fallback from per-user → global
  • resolve_route_for_owner with sandbox owner

- Replace unreachable!() with Status::internal() for defense-in-depth
  in get_inference_bundle when principal variant is unexpected.

- Add unit tests covering per-user ownership scoping logic:
  - scoped_route_name: non-admin, admin, anonymous, none, sandbox cases
  - upsert: non-admin creates scoped route
  - upsert: non-owner blocked from updating another user's route
  - resolve_get_route: falls back to global when no per-user route
  - resolve_get_route: prefers per-user route over global
  - resolve_route_for_owner: sandbox owner resolves per-user route
  - resolve_route_for_owner: falls back without per-user route

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
@pdettori pdettori merged commit 0a01b7b into mvp-v2 Jun 23, 2026
8 of 10 checks passed
@pdettori pdettori deleted the feat/per-user-inference-routes branch June 23, 2026 13:36
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.

1 participant