Skip to content

feat(workspace): add workspace resource model with scoping, membershi…#2243

Draft
derekwaynecarr wants to merge 8 commits into
NVIDIA:mainfrom
derekwaynecarr:decarr/workspace-model
Draft

feat(workspace): add workspace resource model with scoping, membershi…#2243
derekwaynecarr wants to merge 8 commits into
NVIDIA:mainfrom
derekwaynecarr:decarr/workspace-model

Conversation

@derekwaynecarr

@derekwaynecarr derekwaynecarr commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements Phase 1 of RFC 0011 — the workspace and membership model that provides hard isolation boundaries for multi-player OpenShell deployments.

  • Workspace resource: CRUD RPCs (CreateWorkspace, GetWorkspace, ListWorkspaces, DeleteWorkspace) with a default workspace created on gateway startup for backwards compatibility
  • Workspace-scoped resources: workspace field on ObjectMeta — sandboxes, providers, service endpoints, SSH sessions, policies, settings, provider refresh state, and inference routes all inherit workspace
    from context
  • Membership model: AddWorkspaceMember, RemoveWorkspaceMember, ListWorkspaceMembers RPCs with (workspace, principal_subject) → role records; membership records cleaned up on workspace deletion
  • Persistence: Name uniqueness shifts from (object_type, name) to (object_type, workspace, name) via migration 006; existing resources backfilled to default workspace; cross-workspace list_by_type store
    method for infrastructure operations (reconciler, resume, provider refresh)
  • Provider profiles: Two-tier scoping with independent scope listing — workspace custom + built-in, or platform custom + built-in (no merged cross-scope view)
  • Service routing: Endpoint hostnames include workspace prefix ({workspace}--{sandbox}--{service}.{domain}); default workspace preserves current format
  • Inference routes: Rename Cluster* RPCs and messages to Route* (SetInferenceRoute, GetInferenceRoute, InferenceRouteConfig); workspace-scope route storage and lookup; derive workspace from sandbox
    principal for bundle resolution; thread --workspace through CLI inference commands
  • ObjectWorkspace trait: requires_workspace() method with debug_assert! validation in store write helpers to catch empty-workspace bugs in debug builds
  • CLI: --workspace and --all-workspaces flags on all resource commands; WORKSPACE column as first column in list outputs when --all-workspaces (matching kubectl --all-namespaces convention); workspace
    CRUD and membership subcommands; inference help text updated from "gateway-level" to "workspace-level"
  • TUI: Workspace state with [w] key cycling, WORKSPACE column in sandbox/provider tables, workspace indicator in title bar, workspace-scoped gRPC requests

Related Issue

#1977

Changes

  • 50 files changed, +7188 / -886 lines
  • New files: workspace.rs (gRPC handlers + tests), migration 006 (sqlite + postgres), workspace_lifecycle.rs (e2e test), RFC 0011
  • Proto: Workspace, WorkspaceMember messages; workspace CRUD and membership RPCs; workspace field on ObjectMeta; all_workspaces on list requests; inference route rename (Cluster* → Route*) with
    workspace fields on set/get request/response messages

Testing

  • 897 server unit tests passing (11 workspace-specific, 2 inference route workspace isolation)
  • 24 TUI tests passing
  • 7 CLI integration tests passing
  • e2e workspace lifecycle test covering create/list/get/delete and cross-workspace isolation
  • Inference route isolation tests: verify workspace-scoped route resolution (alpha/beta workspaces with different providers/models) and empty bundle for workspace with no route configured

Known remaining work

  • Phase 2: Expanded role model (Platform Admin / Workspace Admin / User) and authorization enforcement — not started, sequential dependency on this PR.

@derekwaynecarr derekwaynecarr requested review from a team, maxamillion and mrunalp as code owners July 13, 2026 12:23
@copy-pr-bot

copy-pr-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@derekwaynecarr derekwaynecarr marked this pull request as draft July 13, 2026 12:26
@derekwaynecarr derekwaynecarr force-pushed the decarr/workspace-model branch 2 times, most recently from 2468c04 to 77ef1fc Compare July 13, 2026 22:31
Thread workspace through compute drivers (Docker, Podman, K8s) with
converged container naming (openshell-{workspace}--{name}-{id}),
workspace labels, and label-based lookup. Fix sandbox-side policy sync
to use learned workspace instead of hardcoding empty string. Fix TUI
all-workspaces view to use per-row workspace for sandbox actions. Add
provider profile to workspace deletion blocking check. Thread workspace
through SSH config generation with workspace-qualified host aliases.

Signed-off-by: Derek Carr <decarr@redhat.com>
Add workspace as a required parameter for resource-scoped operations
(create, get, delete, wait_ready, wait_deleted) and optional for
list operations that support all_workspaces global lookup. Rename
ClusterInferenceConfig to InferenceRouteConfig and set_cluster/get_cluster
to set_route/get_route to match the renamed proto RPCs.

Signed-off-by: Derek Carr <decarr@redhat.com>
@derekwaynecarr derekwaynecarr force-pushed the decarr/workspace-model branch from 77ef1fc to 7bba756 Compare July 14, 2026 18:18
for (object_type, label) in [
(Sandbox::object_type(), "sandbox"),
(Provider::object_type(), "provider"),
(StoredProviderProfile::object_type(), "provider profile"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

From CodeRabbit review:
Include SSH sessions in workspace deletion blockers.

ObjectWorkspace for SshSession marks sessions as workspace-scoped, but deletion does not check SshSession::object_type(). A workspace can therefore be removed while session records still reference it.

     for (object_type, label) in [
         (Sandbox::object_type(), "sandbox"),
         (Provider::object_type(), "provider"),
+        (SshSession::object_type(), "SSH session"),
         (ServiceEndpoint::object_type(), "service"),
         (InferenceRoute::object_type(), "inference route"),
     ] {

Comment thread crates/openshell-server/src/grpc/mod.rs
- Backfill provider_credential_refresh_state in workspace migration
- Set requires_workspace() to true for refresh state
- Reject consecutive hyphens in workspace and sandbox names
- Restore parse_host rejection of consecutive hyphens in all segments
- Pass workspace from SSH session metadata in put_if call
- Require explicit workspace in Python SDK Sandbox() constructor
- Add sandbox name character validation matching workspace rules

Signed-off-by: Derek Carr <decarr@redhat.com>
- Add watch channel to broadcast workspace from policy poll loop to
  denial and activity flush tasks, fixing proposals targeting the
  wrong workspace for non-default sandboxes
- Add set_workspace() to CachedOpenShellClient for pre-seeding
  workspace on fresh connections
- Use selected_provider_workspace() in TUI provider get/delete actions
  instead of current_workspace, fixing cross-workspace misrouting

Signed-off-by: Derek Carr <decarr@redhat.com>
Signed-off-by: Derek Carr <decarr@redhat.com>
…le scope binding

Provider profiles can be global (platform-scoped) or workspace-scoped.
Previously, profile lookups used the ambient request workspace, which
caused pre-existing global profiles (workspace="") to become invisible
after the workspace migration.

Add a `profile_workspace` field to the Provider proto that explicitly
declares where the provider's type profile is stored. The field must be
empty (global) or match the provider's own workspace — cross-workspace
references are rejected. All profile lookup call sites now use
`provider.profile_workspace` instead of the ambient workspace.

Existing serialized providers get `profile_workspace=""` via protobuf
defaults, correctly pointing to their global profiles with no migration.

CLI gains `--global-profile` flag on `provider create`.

Signed-off-by: Derek Carr <decarr@redhat.com>
…-back

handle_revoke_ssh_session passed empty string for workspace in put_if
instead of session.object_workspace(), dropping the workspace on the
revoked session record.

Signed-off-by: Derek Carr <decarr@redhat.com>
The lint handler was passing an empty string to profile_conflict_diagnostics
instead of using the request workspace, so it only checked for conflicts in
global scope rather than the workspace the user specified.

Signed-off-by: Derek Carr <decarr@redhat.com>
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.

2 participants