Root cause: BaseHTTPMiddleware.call_next() runs the endpoint in a
separate async task, which breaks SQLAlchemy's greenlet context.
After session.commit(), the connection is released to the pool.
When session.refresh() tries to acquire a new connection for a
SELECT, it fails with MissingGreenlet because the greenlet spawn
context is not available in the call_next() task.
Fix (two-part):
1. Convert all 4 middleware classes from BaseHTTPMiddleware to pure
ASGI middleware — eliminates call_next() entirely so the entire
request chain runs in the same async context.
2. Set expire_on_commit=False on the session factory — after commit,
objects retain their in-memory values instead of being expired.
This removes the need for session.refresh() entirely.
All session.refresh() calls removed from 11 repository files.
With expire_on_commit=False, post-commit attribute access no longer
triggers lazy loads that would also fail with MissingGreenlet.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>