From ee63ef2d2d3e0214c27f90cf0e3647c9fcf19cad Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 12 May 2026 22:09:04 +0000 Subject: [PATCH] fix(orch): coerce UUID session_id to str + re-export _decode_token Two CI failures introduced by the RBAC merge: 1. SessionLifecycleEvent.session_id was changed from UUID to str so that chat IDs like "session-1778614829220" round-trip through the API response model, but pydantic v2 rejects a UUID instance for a str field at construction time. EventPublisher._emit() passes UUID, so every test that exercises the session pipeline blew up with "Input should be a valid string ... input_type=UUID". Add a before-validator on both SessionLifecycleEvent and SessionEventListResponse that coerces UUID -> str. Cheaper than changing every emit site, and keeps the API contract as plain str. 2. tests/test_auth_deps.py imports _decode_token from dependencies.auth, but the shim was reduced to re-exporting IdentityContext + get_current_identity only. Re-export _decode_token too so the JWT-signature regression test compiles. https://claude.ai/code/session_015Pp147sgmYk83YZEvaqStp --- .../dependencies/auth.py | 1 + .../agent-orchestrator-service/schemas/events.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/services/agent-orchestrator-service/dependencies/auth.py b/services/agent-orchestrator-service/dependencies/auth.py index ffd25fc2..92ec9ff7 100644 --- a/services/agent-orchestrator-service/dependencies/auth.py +++ b/services/agent-orchestrator-service/dependencies/auth.py @@ -6,5 +6,6 @@ """ from platform_shared.rbac import ( # noqa: F401 re-exported IdentityContext, + _decode_token, get_current_identity, ) diff --git a/services/agent-orchestrator-service/schemas/events.py b/services/agent-orchestrator-service/schemas/events.py index 92b0e5bf..7e059c38 100644 --- a/services/agent-orchestrator-service/schemas/events.py +++ b/services/agent-orchestrator-service/schemas/events.py @@ -21,7 +21,7 @@ from typing import Any, ClassVar, Dict, FrozenSet, List, Optional from uuid import UUID, uuid4 -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_validator # ───────────────────────────────────────────────────────────────────────────── @@ -127,6 +127,8 @@ class SessionLifecycleEvent(BaseModel): event_type: EventType # session_id is a free-form string -- the chat sessions use IDs like # "session-1778614829220" and "agent--runtime" which are not UUIDs. + # Pydantic v2 rejects UUID for a str field under default strict-ish mode, + # but lifecycle event emission upstream passes a UUID, so coerce here. session_id: str correlation_id: str = Field(..., description="Shared trace ID across all steps") timestamp: datetime = Field(default_factory=_utcnow) @@ -135,6 +137,11 @@ class SessionLifecycleEvent(BaseModel): summary: str = Field(..., description="Human-readable one-liner for timeline display") payload: Dict[str, Any] = Field(default_factory=dict, description="Full step-specific data") + @field_validator("session_id", mode="before") + @classmethod + def _coerce_session_id_to_str(cls, v: Any) -> Any: + return str(v) if isinstance(v, UUID) else v + # ───────────────────────────────────────────────────────────────────────────── # Domain payload: prompt.received (step 1) @@ -277,6 +284,11 @@ class SessionEventListResponse(BaseModel): event_count: int events: List[SessionLifecycleEvent] + @field_validator("session_id", mode="before") + @classmethod + def _coerce_session_id_to_str(cls, v: Any) -> Any: + return str(v) if isinstance(v, UUID) else v + class SessionTimelineEntry(BaseModel): """Condensed view used in GET /sessions/{id} timeline array."""