feat: global usernames and rename workspace to default_workspace

Users are global entities, not scoped to workspaces. This change:

Track A — Global usernames:
- Change iam_users_by_username to PRIMARY KEY (username), removing
  workspace from the lookup key
- Login looks up username globally, no workspace required
- Username uniqueness is enforced globally, not per-workspace
- Login -w now overrides the JWT workspace (session workspace)
  rather than selecting which user registry to search

Track B — Rename workspace to default_workspace:
- UserRecord.workspace → UserRecord.default_workspace
- Identity.workspace → Identity.default_workspace
- JWT claim "workspace" → "default_workspace"
- IamResponse.resolved_workspace → resolved_default_workspace
- WebSocket auth-ok frame field → default_workspace
- Socket clients read default_workspace from auth-ok
- _user_record_to_dict wire key → default_workspace
- CLI help text and output updated throughout
- Test files updated for renamed fields
This commit is contained in:
Cyber MacGeddon 2026-06-25 16:25:11 +01:00
parent 16f8cfd972
commit a04bb9854c
19 changed files with 101 additions and 113 deletions

View file

@ -25,11 +25,11 @@ from trustgraph.gateway.capabilities import (
class _Identity:
"""Stand-in for auth.Identity — under the IAM contract it has
just ``handle``, ``workspace``, ``principal_id``, ``source``."""
just ``handle``, ``default_workspace``, ``principal_id``, ``source``."""
def __init__(self, handle="user-1", workspace="default"):
def __init__(self, handle="user-1", default_workspace="default"):
self.handle = handle
self.workspace = workspace
self.default_workspace = default_workspace
self.principal_id = handle
self.source = "api-key"
@ -105,14 +105,14 @@ class TestEnforceWorkspace:
async def test_default_fills_from_identity(self):
data = {"operation": "x"}
auth = _allow_auth()
await enforce_workspace(data, _Identity(workspace="default"), auth)
await enforce_workspace(data, _Identity(default_workspace="default"), auth)
assert data["workspace"] == "default"
@pytest.mark.asyncio
async def test_caller_supplied_workspace_kept(self):
data = {"workspace": "acme", "operation": "x"}
auth = _allow_auth()
await enforce_workspace(data, _Identity(workspace="default"), auth)
await enforce_workspace(data, _Identity(default_workspace="default"), auth)
assert data["workspace"] == "acme"
@pytest.mark.asyncio