feat!: require explicit user on every tenant-data endpoint

Remove the implicit jpmschweitzer default tenant (DEFAULT_USER) from
src/core/multi_tenancy.py and every endpoint and request model that
inherited it (~40 endpoints across /query, /wiki, /vector, /graph,
/ingest, /volatile, /documents, /stats, /rag).

- Add validate_required_user() + RequiredUser pydantic type in
  multi_tenancy and a shared require_user FastAPI dependency
  (RequiredUserQuery) that rejects missing, empty, and whitespace-only
  users with 422, following the /maintenance/* pattern.
- Wiki page create / smart-create / dossier request models now require
  user (no fallback in wiki_service).
- /maintenance/cleanup/test-data derives the tenant from the page path
  instead of using the production tenant collection.
- Wiki.js change listener skips changes when no tenant user can be
  derived from the notification email instead of defaulting to the
  production tenant.
- Consolidation service internal helpers no longer default to the
  production tenant.
- Tool catalog marks user as required with honest descriptions.
- OpenAPI descriptions updated honestly; CHANGELOG notes that callers
  (tatlock, Scheduler ingest tasks) must now send explicit user.
- Offline tests: 422 coverage for query/body endpoints, required-user
  validator tests; updated legacy tests that assumed a default tenant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 11:06:11 +02:00
co-authored by Claude Fable 5
parent a66d801abd
commit 84e9185371
27 changed files with 481 additions and 153 deletions
+12 -6
View File
@@ -8,6 +8,8 @@ from pydantic import BaseModel, Field
from typing import List, Dict, Any, Optional
from datetime import datetime
from src.core.multi_tenancy import RequiredUser
class GraphNode(BaseModel):
"""Graph node representation."""
@@ -45,9 +47,13 @@ class CypherQueryRequest(BaseModel):
default_factory=dict,
description="Query parameters"
)
user: str = Field(
default="jpmschweitzer",
description="User for filtering (automatically scopes query)"
user: RequiredUser = Field(
...,
description=(
"User identifier (tenant). Required. NOTE: raw Cypher queries are NOT "
"automatically scoped to this tenant — the endpoint is read-only and "
"intended for admin/debug use. Results may span all tenants."
)
)
@@ -61,9 +67,9 @@ class CypherQueryResponse(BaseModel):
class UpdateFromPageRequest(BaseModel):
"""Request to update graph from a wiki page."""
page_id: int = Field(..., description="Wiki page ID to process")
user: str = Field(
default="jpmschweitzer",
description="User identifier for namespace scoping"
user: RequiredUser = Field(
...,
description="User identifier (tenant). Required — graph writes are scoped to this tenant's labels."
)
force_refresh: bool = Field(
default=False,