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:
@@ -7,6 +7,8 @@ Provides models for semantic search, document chunks, and embeddings.
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
from src.core.multi_tenancy import RequiredUser
|
||||
|
||||
|
||||
class DocumentChunk(BaseModel):
|
||||
"""Document chunk with embedding."""
|
||||
@@ -32,7 +34,7 @@ class SearchResult(BaseModel):
|
||||
class SearchRequest(BaseModel):
|
||||
"""Semantic search request."""
|
||||
query: str = Field(..., min_length=1, description="Search query")
|
||||
user: str = Field(default="jpmschweitzer", description="User identifier")
|
||||
user: RequiredUser = Field(..., description="User identifier (tenant). Required — search is scoped to this tenant's collection.")
|
||||
limit: int = Field(default=10, ge=1, le=100, description="Maximum results")
|
||||
score_threshold: float = Field(default=0.5, ge=0.0, le=1.0, description="Minimum similarity score")
|
||||
|
||||
@@ -48,9 +50,9 @@ class SearchResponse(BaseModel):
|
||||
class VectorUpdateRequest(BaseModel):
|
||||
"""Request to update vectors 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 — vectors are written to this tenant's collection."
|
||||
)
|
||||
force_refresh: bool = Field(
|
||||
default=False,
|
||||
@@ -89,7 +91,7 @@ class CollectionListResponse(BaseModel):
|
||||
class DeletePageChunksRequest(BaseModel):
|
||||
"""Request to delete all chunks for a page."""
|
||||
page_id: int = Field(..., description="Wiki page ID")
|
||||
user: str = Field(default="jpmschweitzer", description="User identifier")
|
||||
user: RequiredUser = Field(..., description="User identifier (tenant). Required — deletion is scoped to this tenant's collection.")
|
||||
|
||||
|
||||
class DeletePageChunksResponse(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user