[BUG FIX][DB]: Restore transaction control to get_db() for middleware sessions (#3731)#3813
Conversation
|
Hi @ja8zyjits , Can you please help me review this PR? If this gets merged, I can pick up issue #3622 . |
Code Review: PR #3813 - Transaction Management FixReviewer: AI Code Review Assistant 📋 Executive SummaryVerdict: ✅ APPROVED This PR successfully addresses the critical transaction management violation identified in issue #3731. The implementation correctly restores transaction control to Key Strengths
Trade-offs Documented
🎯 Requirements VerificationIssue #3731 Acceptance Criteria
All 8 acceptance criteria are met. ✅ 💻 Code Changes Review1.
|
| # | Test Name | Purpose | Status |
|---|---|---|---|
| 1 | test_middleware_does_not_commit_shared_session |
Verifies middleware doesn't commit | ✅ |
| 2 | test_get_db_commits_middleware_session_on_success |
Verifies get_db() commits on success | ✅ |
| 3 | test_get_db_rollsback_middleware_session_on_error |
Verifies get_db() rolls back on error (CORE FIX) | ✅ |
| 4 | test_get_db_invalidates_broken_connection_middleware_session |
Verifies connection invalidation | ✅ |
| 5 | test_full_request_flow_with_observability |
End-to-end integration test | ✅ |
| 6 | test_observability_data_lost_on_error_is_acceptable |
Documents acceptable trade-off | ✅ |
| 7 | test_get_db_invalidates_broken_connection_double_failure |
Double failure edge case | ✅ |
| 8 | test_get_db_inactive_session_skips_commit |
Inactive session edge case | ✅ |
Assessment: ✅ OUTSTANDING
Test Quality:
- Comprehensive coverage: Tests success paths, error paths, and edge cases
- Tests the actual bug: Test 3 verifies the core fix (rollback on error)
- Integration testing: Test 5 validates full request flow
- Edge case coverage: Tests broken connections, double failures, inactive sessions
- Clear naming: Descriptive test names explain what's being tested
- Good mocking: Proper use of mocks and assertions
- Trade-off documentation: Test 6 explicitly documents observability data loss
Particularly Strong Points:
- Test 3 is the most critical - it verifies the exact bug scenario from issue [BUG][DB]: Transaction management violation in PR 3600 — get_db() loses control over commits #3731
- Test 6 documents the design trade-off (observability data loss is acceptable)
- Test 7 covers the rare double-failure scenario (both rollback and invalidate fail)
- Tests verify the fix location (get_db commits, not middleware)
Test Results:
- ✅ 8/8 new transaction control tests passed
- ✅ 28/28 observability middleware tests passed (27 existing + 1 updated)
- ✅ 853/853 all middleware tests passed
Updated Tests (2 files)
-
test_observability_middleware_creates_request_scoped_session- Updated assertion: Middleware no longer commits
- Status: ✅ PASS
-
test_get_db_reuses_middleware_session- Updated assertion: get_db() now commits
- Status: ✅ PASS
Assessment: ✅ GOOD
- Properly updated to match new behavior
- Maintains test integrity
- No breaking changes
🔍 Deep Dive: Critical Bug Fix
The Original Bug
Scenario from Issue #3731:
@app.post("/items")
async def create_item(item: Item, db: Session = Depends(get_db)):
db_item = DBItem(**item.dict())
db.add(db_item)
if not validate(db_item):
raise ValueError("Invalid") # Developer expects rollback
# BUG in PR https://github.com/IBM/mcp-context-forge/pull/3600: Middleware commits anyway!
# Invalid item is permanently in database!Why this was critical:
- ❌ Invalid data could be committed despite validation failures
- ❌ Partial operations could be committed unexpectedly
- ❌ Route handlers lost control over transaction boundaries
- ❌ Violated the design requirement from issue [BUG][PERFORMANCE]: Observability middleware opens duplicate DB session per request #3467
The Fix Analysis
Key Code in main.py:3153-3162:
except Exception:
# ROLLBACK on error (Issue #3731)
try:
db.rollback()
except Exception:
# Connection may be broken - invalidate to prevent reuse
try:
db.invalidate()
except Exception:
pass # nosec B110 - Best effort cleanup, errors already raised
raiseThis correctly fixes the bug because:
-
✅ Exception in route handler is caught
- Any exception from the route handler is caught by get_db()
-
✅
db.rollback()is called immediately- Prevents partial commits of invalid data
- Rolls back all changes in the transaction
-
✅ Exception is re-raised
- Propagates to caller (FastAPI error handler)
- Client receives proper error response
-
✅ Middleware no longer commits after rollback
- Separation of concerns maintained
- Transaction control stays with get_db()
-
✅ Connection cleanup on failure
- Broken connections are invalidated
- Prevents pool corruption
Test Verification
From test_get_db_rollsback_middleware_session_on_error:
# Simulate exception in route handler
with pytest.raises(ValueError):
gen.throw(ValueError("Validation failed"))
# CRITICAL: Verify get_db() called rollback
mock_session.rollback.assert_called_once()
# Verify get_db() did NOT call commit
mock_session.commit.assert_not_called()✅ The fix is correct and tested.
🔒 Security Analysis
Issue #3731 Security Requirements
The issue states:
"Design should respect existing commit/rollback and lifecycle semantics."
Verification:
| Security Concern | Before PR #3600 | After PR #3600 (BROKEN) | After PR 3813 (FIXED) |
|---|---|---|---|
| Transaction Isolation | ✅ Per-request | ✅ Per-request | ✅ Per-request |
| Data Integrity | ✅ Rollback on error | ❌ BUG: Commits on error | ✅ Rollback on error |
| Predictable Semantics | ✅ get_db() controls | ❌ Middleware controls | ✅ get_db() controls |
| Auth/RBAC |
PR #3600 introduced a transaction management violation where ObservabilityMiddleware commits the shared database session instead of get_db(), breaking the established contract where get_db() controls transaction boundaries. This creates data integrity risks where failed validations can be committed to the database. This fix restores the correct behavior: - Middleware manages session lifecycle (create/close) - get_db() manages transactions (commit/rollback) Changes: - Remove commit logic from ObservabilityMiddleware (observability_middleware.py:210-216) - Add commit/rollback handling to get_db() for middleware sessions (main.py:3137-3164) - Update get_db() docstring to document transaction control responsibility - Update 2 existing tests to reflect new behavior - Add 7 comprehensive tests for transaction semantics Security implications: - Fixes data integrity bug where invalid data could be committed - Maintains proper transaction isolation per request - Preserves connection invalidation on broken connections - No impact on auth/RBAC (middleware runs before route handlers) Trade-offs: - Observability data (traces/spans) is rolled back on errors (acceptable - best-effort tracing) Closes #3731 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
Remove unused AsyncMock import and unused variable assignments flagged by ruff (F401, F841). Apply isort/black formatting. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
2143b28 to
28b19c7
Compare
crivetimihai
left a comment
There was a problem hiding this comment.
Review: Approved
Changes Made During Review
- Rebased onto main — clean, no conflicts.
- Fixed lint violations in the new test file (
test_observability_middleware_transactions.py):- Removed unused
AsyncMockimport (ruff F401) - Removed 4 unused
dbvariable assignments (ruff F841) - Applied
isort/blackformatting (parenthesizedwithblocks, import grouping)
- Removed unused
Code Review Summary
Design: The separation of concerns (middleware = lifecycle, get_db() = transactions) is sound and consistent with the existing fallback path pattern. The reuse path now mirrors the fallback path exactly for commit/rollback/invalidate.
Correctness: For the bug being fixed (#3731 — route handler raises, middleware commits invalid data), the fix is effective. get_db() rolls back during FastAPI dependency cleanup, which runs before middleware's post-response code executes.
Consistency: Verified against 60+ explicit db.commit() calls across services, 619 Depends(get_db) usages, and transaction patterns in other middleware (auth_middleware.py, rbac.py, token_scoping.py).
Security: No regression — transaction isolation maintained, auth/RBAC unaffected, connection invalidation preserved.
Test Coverage: 100% differential coverage on new code. 28/28 tests pass.
Integration Testing (localhost:8080, 3 gateway instances)
Tested with observability both disabled and enabled:
- API: servers, tools, gateways, resources, prompts, A2A agents — all working
- MCP Protocol: initialize, tools/list, tools/call — all working
- CRUD: full create/read/update/delete team cycle — commits and deletes persisted correctly
- Auth: unauthenticated and invalid tokens correctly rejected (401)
- Concurrency: 20 concurrent MCP tool calls with observability — 20/20 succeeded
- Observability Dashboard: 26 traces persisted and visible in the UI with status codes and timing
- Docker logs: zero errors across all 3 gateway instances
- Browser console: zero errors
Note on _safe_commit in ObservabilityService
The observability service methods (start_trace, end_span, etc.) call _safe_commit() which commits the session eagerly. This is pre-existing behavior (untouched by this PR) and does not undermine the fix — for the error case, get_db() rolls back before _safe_commit runs on end_span/end_trace. The late-failure streaming edge case is pre-existing and out of scope for this focused bug fix.
Implements session reuse pattern from PR #3600 and PR #3813 to achieve 1 shared database session per request across all middleware layers. **Changes:** - Auth middleware: Added _get_or_create_session() helper to reuse request.state.db from ObservabilityMiddleware (lines 134, 159, 213) - RBAC middleware: Updated deprecated get_db() to accept optional request parameter and reuse middleware session when available - Transaction control: Delegated all commit/rollback to get_db() per PR #3813 (removed db.commit() from auth middleware) - Added 7 unit tests for auth session reuse patterns - Added 7 unit tests for RBAC get_db() deprecation - Added 6 integration tests for end-to-end session sharing validation **Impact:** - Reduces session creation from 4-6 per request to 1 per request - Prevents connection pool exhaustion under load - Achieves 100% test coverage (435 statements, 0 missing) **Security:** - Transaction isolation maintained (get_db() controls all commits) - Connection invalidation for PgBouncer compatibility - Backwards compatible (existing dependency overrides work) Closes #3622 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
… sessions (#3731) (#3813) * fix(db): restore transaction control to get_db() for middleware sessions PR #3600 introduced a transaction management violation where ObservabilityMiddleware commits the shared database session instead of get_db(), breaking the established contract where get_db() controls transaction boundaries. This creates data integrity risks where failed validations can be committed to the database. This fix restores the correct behavior: - Middleware manages session lifecycle (create/close) - get_db() manages transactions (commit/rollback) Changes: - Remove commit logic from ObservabilityMiddleware (observability_middleware.py:210-216) - Add commit/rollback handling to get_db() for middleware sessions (main.py:3137-3164) - Update get_db() docstring to document transaction control responsibility - Update 2 existing tests to reflect new behavior - Add 7 comprehensive tests for transaction semantics Security implications: - Fixes data integrity bug where invalid data could be committed - Maintains proper transaction isolation per request - Preserves connection invalidation on broken connections - No impact on auth/RBAC (middleware runs before route handlers) Trade-offs: - Observability data (traces/spans) is rolled back on errors (acceptable - best-effort tracing) Closes #3731 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * test(db): add coverage for double-failure edge case in get_db() Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(tests): clean up lint violations in transaction control tests Remove unused AsyncMock import and unused variable assignments flagged by ruff (F401, F841). Apply isort/black formatting. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
… sessions (#3731) (#3813) * fix(db): restore transaction control to get_db() for middleware sessions PR #3600 introduced a transaction management violation where ObservabilityMiddleware commits the shared database session instead of get_db(), breaking the established contract where get_db() controls transaction boundaries. This creates data integrity risks where failed validations can be committed to the database. This fix restores the correct behavior: - Middleware manages session lifecycle (create/close) - get_db() manages transactions (commit/rollback) Changes: - Remove commit logic from ObservabilityMiddleware (observability_middleware.py:210-216) - Add commit/rollback handling to get_db() for middleware sessions (main.py:3137-3164) - Update get_db() docstring to document transaction control responsibility - Update 2 existing tests to reflect new behavior - Add 7 comprehensive tests for transaction semantics Security implications: - Fixes data integrity bug where invalid data could be committed - Maintains proper transaction isolation per request - Preserves connection invalidation on broken connections - No impact on auth/RBAC (middleware runs before route handlers) Trade-offs: - Observability data (traces/spans) is rolled back on errors (acceptable - best-effort tracing) Closes #3731 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * test(db): add coverage for double-failure edge case in get_db() Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(tests): clean up lint violations in transaction control tests Remove unused AsyncMock import and unused variable assignments flagged by ruff (F401, F841). Apply isort/black formatting. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Implements session reuse pattern from PR #3600 and PR #3813 to achieve 1 shared database session per request across all middleware layers. **Changes:** - Auth middleware: Added _get_or_create_session() helper to reuse request.state.db from ObservabilityMiddleware (lines 134, 159, 213) - RBAC middleware: Updated deprecated get_db() to accept optional request parameter and reuse middleware session when available - Transaction control: Delegated all commit/rollback to get_db() per PR #3813 (removed db.commit() from auth middleware) - Added 7 unit tests for auth session reuse patterns - Added 7 unit tests for RBAC get_db() deprecation - Added 6 integration tests for end-to-end session sharing validation **Impact:** - Reduces session creation from 4-6 per request to 1 per request - Prevents connection pool exhaustion under load - Achieves 100% test coverage (435 statements, 0 missing) **Security:** - Transaction isolation maintained (get_db() controls all commits) - Connection invalidation for PgBouncer compatibility - Backwards compatible (existing dependency overrides work) Closes #3622 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
Implements session reuse pattern from PR #3600 and PR #3813 to achieve 1 shared database session per request across all middleware layers. **Changes:** - Auth middleware: Added _get_or_create_session() helper to reuse request.state.db from ObservabilityMiddleware (lines 134, 159, 213) - RBAC middleware: Updated deprecated get_db() to accept optional request parameter and reuse middleware session when available - Transaction control: Delegated all commit/rollback to get_db() per PR #3813 (removed db.commit() from auth middleware) - Added 7 unit tests for auth session reuse patterns - Added 7 unit tests for RBAC get_db() deprecation - Added 6 integration tests for end-to-end session sharing validation **Impact:** - Reduces session creation from 4-6 per request to 1 per request - Prevents connection pool exhaustion under load - Achieves 100% test coverage (435 statements, 0 missing) **Security:** - Transaction isolation maintained (get_db() controls all commits) - Connection invalidation for PgBouncer compatibility - Backwards compatible (existing dependency overrides work) Closes #3622 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
Implements session reuse pattern from PR #3600 and PR #3813 to achieve 1 shared database session per request across all middleware layers. **Changes:** - Auth middleware: Added _get_or_create_session() helper to reuse request.state.db from ObservabilityMiddleware (lines 134, 159, 213) - RBAC middleware: Updated deprecated get_db() to accept optional request parameter and reuse middleware session when available - Transaction control: Delegated all commit/rollback to get_db() per PR #3813 (removed db.commit() from auth middleware) - Added 7 unit tests for auth session reuse patterns - Added 7 unit tests for RBAC get_db() deprecation - Added 6 integration tests for end-to-end session sharing validation **Impact:** - Reduces session creation from 4-6 per request to 1 per request - Prevents connection pool exhaustion under load - Achieves 100% test coverage (435 statements, 0 missing) **Security:** - Transaction isolation maintained (get_db() controls all commits) - Connection invalidation for PgBouncer compatibility - Backwards compatible (existing dependency overrides work) Closes #3622 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
#3886) * fix: eliminate duplicate DB sessions in auth and RBAC middleware Implements session reuse pattern from PR #3600 and PR #3813 to achieve 1 shared database session per request across all middleware layers. **Changes:** - Auth middleware: Added _get_or_create_session() helper to reuse request.state.db from ObservabilityMiddleware (lines 134, 159, 213) - RBAC middleware: Updated deprecated get_db() to accept optional request parameter and reuse middleware session when available - Transaction control: Delegated all commit/rollback to get_db() per PR #3813 (removed db.commit() from auth middleware) - Added 7 unit tests for auth session reuse patterns - Added 7 unit tests for RBAC get_db() deprecation - Added 6 integration tests for end-to-end session sharing validation **Impact:** - Reduces session creation from 4-6 per request to 1 per request - Prevents connection pool exhaustion under load - Achieves 100% test coverage (435 statements, 0 missing) **Security:** - Transaction isolation maintained (get_db() controls all commits) - Connection invalidation for PgBouncer compatibility - Backwards compatible (existing dependency overrides work) Closes #3622 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix: ensure auth logs are committed in hard-deny paths Fixes critical bug where auth failure logs were lost when API requests received hard-deny responses (401/403) that return JSONResponse immediately without reaching get_db(). **Root Cause:** - Auth middleware writes logs to session but delegated commit to get_db() - Hard-deny API responses return JSONResponse immediately (lines 243-247) - Route handler never runs, get_db() never called, logs never committed - Browser requests work fine (continue to route handler at line 236) **Fix:** - Added db.commit() after logging in both success and failure paths - Logs persist immediately, even if request doesn't reach get_db() - For requests that continue to route handler, get_db() commits again (no-op) - SQLAlchemy allows multiple commits - second commit is safe **Test Coverage:** - Added regression test: test_auth_failure_logs_committed_before_hard_deny_api_response - All 29 auth middleware tests pass - Maintained 100% code coverage Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(build): use system uv binary consistently in Makefile Replace hardcoded 'uv' commands with $(UV_BIN) variable reference across all targets to ensure consistent resolution of the uv binary path. Export UV_BIN to make it available to recursive make calls. This fixes build failures where make targets tried to execute 'uv' from inside the activated virtual environment, where it doesn't exist. The uv tool is a system-level package manager and should be resolved from the system PATH or ~/.local/bin. Changes: - Use $(UV_BIN) in install, install-dev, install-db, update targets - Use $(UV_BIN) in ensure_pip_package macro (fixes recursive make) - Use $(UV_BIN) in sbom, alembic, pypiserver, maturin targets - Export UV_BIN for visibility in child make processes Benefits: - Fixes "No such file or directory" errors for uv - Makes Makefile more robust across different uv installations - Maintains existing fallback logic (PATH or ~/.local/bin/uv) - No breaking changes for existing setups Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(auth): prevent stale session reference and ensure log persistence Fix two bugs in auth middleware session handling: 1. _get_or_create_session() stored newly created sessions in request.state.db but then closed them after logging, leaving a stale closed-session reference for downstream get_db() to find. Remove the request.state.db assignment for owned sessions so route handlers create their own sessions via get_db(). 2. Generic Exception path (non-HTTP auth failures) did not commit security logs before closing the owned session, silently losing log entries. Add db.commit() consistent with the success and hard-deny paths. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(auth): rollback shared session on logging failure and rewrite integration tests Address two findings from code review: 1. When security logging raises an exception (commit failure, connection error), the shared session from ObservabilityMiddleware is left in PendingRollbackError state. Downstream call_next()/get_db() then inherits a broken session. Add db.rollback() (with invalidate() fallback) in all three exception handlers — matching the pattern used by ObservabilityMiddleware and main.py:get_db(). 2. Rewrite integration tests: the originals targeted nonexistent routes (/api/v1/servers, /admin/llm/providers) and had assertions too weak to catch regressions (session_count <= 1 passes when 0 sessions are created). New tests directly exercise _get_or_create_session(), rbac.get_db() reuse, shared-session rollback, and the full middleware stack via /health. Test coverage: 100% (460 statements, 0 missing) across both auth middleware and rbac modules. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * chore: update secrets baseline after rebase Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * chore: exclude .npmrc from sdist to fix check-manifest Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
#3886) * fix: eliminate duplicate DB sessions in auth and RBAC middleware Implements session reuse pattern from PR #3600 and PR #3813 to achieve 1 shared database session per request across all middleware layers. **Changes:** - Auth middleware: Added _get_or_create_session() helper to reuse request.state.db from ObservabilityMiddleware (lines 134, 159, 213) - RBAC middleware: Updated deprecated get_db() to accept optional request parameter and reuse middleware session when available - Transaction control: Delegated all commit/rollback to get_db() per PR #3813 (removed db.commit() from auth middleware) - Added 7 unit tests for auth session reuse patterns - Added 7 unit tests for RBAC get_db() deprecation - Added 6 integration tests for end-to-end session sharing validation **Impact:** - Reduces session creation from 4-6 per request to 1 per request - Prevents connection pool exhaustion under load - Achieves 100% test coverage (435 statements, 0 missing) **Security:** - Transaction isolation maintained (get_db() controls all commits) - Connection invalidation for PgBouncer compatibility - Backwards compatible (existing dependency overrides work) Closes #3622 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix: ensure auth logs are committed in hard-deny paths Fixes critical bug where auth failure logs were lost when API requests received hard-deny responses (401/403) that return JSONResponse immediately without reaching get_db(). **Root Cause:** - Auth middleware writes logs to session but delegated commit to get_db() - Hard-deny API responses return JSONResponse immediately (lines 243-247) - Route handler never runs, get_db() never called, logs never committed - Browser requests work fine (continue to route handler at line 236) **Fix:** - Added db.commit() after logging in both success and failure paths - Logs persist immediately, even if request doesn't reach get_db() - For requests that continue to route handler, get_db() commits again (no-op) - SQLAlchemy allows multiple commits - second commit is safe **Test Coverage:** - Added regression test: test_auth_failure_logs_committed_before_hard_deny_api_response - All 29 auth middleware tests pass - Maintained 100% code coverage Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(build): use system uv binary consistently in Makefile Replace hardcoded 'uv' commands with $(UV_BIN) variable reference across all targets to ensure consistent resolution of the uv binary path. Export UV_BIN to make it available to recursive make calls. This fixes build failures where make targets tried to execute 'uv' from inside the activated virtual environment, where it doesn't exist. The uv tool is a system-level package manager and should be resolved from the system PATH or ~/.local/bin. Changes: - Use $(UV_BIN) in install, install-dev, install-db, update targets - Use $(UV_BIN) in ensure_pip_package macro (fixes recursive make) - Use $(UV_BIN) in sbom, alembic, pypiserver, maturin targets - Export UV_BIN for visibility in child make processes Benefits: - Fixes "No such file or directory" errors for uv - Makes Makefile more robust across different uv installations - Maintains existing fallback logic (PATH or ~/.local/bin/uv) - No breaking changes for existing setups Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(auth): prevent stale session reference and ensure log persistence Fix two bugs in auth middleware session handling: 1. _get_or_create_session() stored newly created sessions in request.state.db but then closed them after logging, leaving a stale closed-session reference for downstream get_db() to find. Remove the request.state.db assignment for owned sessions so route handlers create their own sessions via get_db(). 2. Generic Exception path (non-HTTP auth failures) did not commit security logs before closing the owned session, silently losing log entries. Add db.commit() consistent with the success and hard-deny paths. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(auth): rollback shared session on logging failure and rewrite integration tests Address two findings from code review: 1. When security logging raises an exception (commit failure, connection error), the shared session from ObservabilityMiddleware is left in PendingRollbackError state. Downstream call_next()/get_db() then inherits a broken session. Add db.rollback() (with invalidate() fallback) in all three exception handlers — matching the pattern used by ObservabilityMiddleware and main.py:get_db(). 2. Rewrite integration tests: the originals targeted nonexistent routes (/api/v1/servers, /admin/llm/providers) and had assertions too weak to catch regressions (session_count <= 1 passes when 0 sessions are created). New tests directly exercise _get_or_create_session(), rbac.get_db() reuse, shared-session rollback, and the full middleware stack via /health. Test coverage: 100% (460 statements, 0 missing) across both auth middleware and rbac modules. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * chore: update secrets baseline after rebase Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * chore: exclude .npmrc from sdist to fix check-manifest Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
🔗 Related Issue
Closes #3731
Unblocks #3622
📝 Summary
Problem: PR #3600 successfully eliminated duplicate DB sessions but introduced a critical transaction management violation. The middleware now commits the shared session, breaking the established pattern where
get_db()controls transaction boundaries. This creates data integrity risks where invalid data can be committed despite validation failures.Solution: Restore transaction control to
get_db()while preserving the single-session-per-request optimization. Middleware manages lifecycle (create/close),get_db()manages transactions (commit/rollback).🐞 Problem Details
What Went Wrong
Before PR #3600 (Correct):
After PR #3600 (Broken):
The Bug
In
observability_middleware.pylines 210-216, middleware commits the session:In
main.pylines 3137-3144, get_db() abdicates responsibility:Data Integrity Risk
Scenario: Invalid data gets committed
Root Cause
From issue #3467 requirement:
PR #3600 correctly shared the session but incorrectly moved transaction control from
get_db()to middleware, violating this requirement.✅ Solution
Design Principle
Separation of Concerns:
Implementation
1. Remove commit from ObservabilityMiddleware (
observability_middleware.py:210-216)2. Add commit/rollback to get_db() (
main.py:3137-3164)3. Update get_db() docstring
Added clear documentation explaining:
get_db()ALWAYS controls transactions (for both owned and reused sessions)get_db()manages transactions4. Update tests
🔒 Security Analysis
No Security Regression ✅
get_db()exception handler performs rollback before re-raisedb.invalidate()pattern preserved in error pathsrequest.state.dbcleared in middleware finally blockAcceptable Trade-off⚠️
Observability data lost on errors: When a route handler fails,
get_db()rolls back the entire transaction, including observability traces/spans. This is acceptable because:🧪 Testing
New Tests (8 total)
File:
tests/unit/mcpgateway/middleware/test_observability_middleware_transactions.pyUpdated Tests (2)
test_observability_middleware_creates_request_scoped_session- Middleware no longer commitstest_get_db_reuses_middleware_session- get_db() now commitsTest Results
🎯 Verification
Code Quality - ALL PASSING ✅
🎨 Type of Change
📊 Impact
Fixed
get_db()for predictable semanticsPreserved
Trade-off
🔄 Relationship to Other Work
✅ Checklist
make autoflake isort black)