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
+2 -2
View File
@@ -398,7 +398,7 @@ class ConsolidationService:
query: str,
web_results: List[Dict[str, Any]],
keywords: List[str],
user: str = "jpmschweitzer"
user: str
) -> Optional[Dict[str, Any]]:
"""
Analyze web results with Ollama for novel information.
@@ -977,7 +977,7 @@ JSON:"""
query: str,
web_results: List[Dict[str, Any]],
keywords: List[str],
user: str = "jpmschweitzer"
user: str
) -> MemoryRoutingResult:
"""
Unified classification of web results for memory routing.
+11 -2
View File
@@ -103,8 +103,17 @@ class WikiChangeListener:
}
event = event_map.get(operation, 'page.update')
# Extract user from email
user = user_email.split('@')[0] if '@' in user_email else 'jpmschweitzer'
# Extract user from email. There is NO default tenant: if no user
# can be derived from the notification, skip processing instead of
# attributing the change to an arbitrary tenant.
if '@' in user_email and user_email.split('@')[0].strip():
user = user_email.split('@')[0].strip()
else:
logger.warning(
f"Skipping page {page_id} change: cannot derive tenant user "
f"from notification email {user_email!r}"
)
return
# Process the change
await self._process_page_change(
+2 -2
View File
@@ -12,7 +12,7 @@ from typing import List, Optional, Dict, Any
import logging
from src.clients.wikijs_client import WikiJSClient
from src.core.multi_tenancy import get_wikijs_namespace, validate_user_id, DEFAULT_USER
from src.core.multi_tenancy import get_wikijs_namespace, validate_user_id
from src.models.wiki import (
WikiPage, WikiPageSummary, WikiPageList,
WikiPageCreate, WikiPageUpdate,
@@ -184,7 +184,7 @@ class WikiService:
Raises:
ValueError: If creation fails
"""
user = page_data.user or DEFAULT_USER
user = page_data.user
# Ensure path is in user's namespace
full_path = self._ensure_user_path(page_data.path, user)