[F089] honest WorkSession agent_id nullability across the read path

The work_sessions.agent_id column is nullable=True with ondelete=SET
NULL — deleting an agent nulls the FK on every session it ever held. The
ORM annotation lied (Mapped[UUID] non-optional), the converter papered
over the lie (typing_cast to a non-optional UUID), and the response
model rejected None outright (WorkSessionResponse.agent_id: UUID). A
session whose agent had been deleted crashed the GET endpoint with a
pydantic ValidationError instead of serializing agent_id: null.

Make the read path honest end-to-end:
- WorkSessionTable.agent_id: Mapped[UUID | None] (matches the column).
- WorkSessionResponse.agent_id: UUID | None (serializes null, no crash).
- session_to_response passes agent_id via typing_cast('UUID | None', ...)
  to bridge SQLAlchemy's UUID[Any] to stdlib uuid.UUID while preserving
  None-ness (the cast stays for the same mypy-plugin reason every other
  field uses one; it no longer narrows away None).

WorkSessionCreate.agent_id stays UUID — at create time the claiming
agent is always known. The unused WorkSession pydantic read model is
left as-is (never materialized from a DB row). task.py:_needs_revision_dev
already None-guards ws.agent_id via to_python_uuid (returns None -> skip).
This commit is contained in:
Renn F
2026-06-28 19:55:33 +02:00
parent 27870e7953
commit 29f8669c09
3 changed files with 70 additions and 3 deletions
+4 -2
View File
@@ -28,7 +28,9 @@ class WorkSessionResponse(BaseModel):
id: UUID
project_id: UUID
task_id: UUID
agent_id: UUID
# Nullable in the DB (ondelete SET NULL when an agent is deleted); a session
# whose agent was deleted serializes agent_id as null, not a crash.
agent_id: UUID | None
# Branch management
branch_name: str
@@ -129,7 +131,7 @@ def session_to_response(session: "WorkSessionTable") -> WorkSessionResponse:
id=typing_cast("UUID", session.id),
project_id=typing_cast("UUID", session.project_id),
task_id=typing_cast("UUID", session.task_id),
agent_id=typing_cast("UUID", session.agent_id),
agent_id=typing_cast("UUID | None", session.agent_id),
branch_name=str(session.branch_name),
base_branch=str(session.base_branch),
target_branch=str(session.target_branch),
+1 -1
View File
@@ -816,7 +816,7 @@ class WorkSessionTable(Base):
nullable=False,
index=True,
)
agent_id: Mapped[UUID] = mapped_column(
agent_id: Mapped[UUID | None] = mapped_column(
UUID(as_uuid=True),
ForeignKey("agents.id", ondelete="SET NULL"),
nullable=True,