feat: add auto-event emission to CapiscioMiddleware#33
Conversation
Add opt-in automatic event emission to the FastAPI/Starlette middleware. When an EventEmitter is provided, the middleware automatically emits: - request.received: on every inbound request - verification.success/failed: after badge verification - request.completed: after response with status_code and duration_ms Design: - Fully opt-in: no emitter = no events (backward compatible) - Safe: emitter errors never break request handling - Standardized fields: method, path, caller_did, duration_ms, status_code
|
✅ Documentation validation passed!
|
|
✅ All checks passed! Ready for review. |
|
✅ SDK server contract tests passed (test_server_integration.py). Cross-product scenarios are validated in capiscio-e2e-tests. |
There was a problem hiding this comment.
Pull request overview
Adds opt-in automatic observability event emission to the FastAPI/Starlette CapiscioMiddleware so request lifecycle and verification outcomes can be tracked without manual instrumentation.
Changes:
- Extend
CapiscioMiddlewareto accept an optionalEventEmitterand emit standardized middleware lifecycle events. - Add new middleware-related event type constants to
EventEmitter. - Add unit tests validating auto-event emission across success/failure/excluded-path scenarios.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
capiscio_sdk/integrations/fastapi.py |
Adds emitter support and emits request.received, verification.*, and request.completed events around middleware execution. |
capiscio_sdk/events.py |
Introduces middleware auto-event type constants on EventEmitter. |
tests/unit/test_fastapi_integration.py |
Adds TestAutoEvents coverage for auto-emission behavior using a mocked EventEmitter. |
| def test_auto_events_on_missing_badge_block(self): | ||
| """Emitter receives request.received and verification.failed when badge missing.""" | ||
| guard = MagicMock() | ||
| guard.agent_id = "test-agent" | ||
|
|
||
| emitter = MagicMock(spec=EventEmitter) | ||
|
|
||
| app = self._make_app(guard, emitter) | ||
| client = TestClient(app) | ||
|
|
||
| response = client.post("/tasks/send", json={"msg": "hi"}) |
There was a problem hiding this comment.
Auto-events are tested for missing-badge in block mode and for verification failure in log mode, but there’s no test covering missing-badge behavior in fail_mode="log"/"monitor" with an emitter. Add a test to lock in whether verification.failed should be emitted (per PR description) while still allowing the request.
| import json | ||
| import pytest | ||
| from unittest.mock import MagicMock, patch | ||
| from unittest.mock import MagicMock, patch, call |
There was a problem hiding this comment.
call is imported from unittest.mock but never used in this test module; this will be flagged by ruff (F401) and should be removed (or actually used if you intended to assert call ordering).
| from unittest.mock import MagicMock, patch, call | |
| from unittest.mock import MagicMock, patch |
| if self.fail_mode in ("log", "monitor"): | ||
| logger.warning(f"Missing X-Capiscio-Badge header for {request.url.path} ({self.fail_mode} mode)") | ||
| request.state.agent = None | ||
| request.state.agent_id = None | ||
| return await call_next(request) | ||
| response = await call_next(request) |
There was a problem hiding this comment.
In the missing-badge branch for fail_mode = "log"/"monitor", the middleware currently emits request.received and request.completed but does not emit verification.failed. This conflicts with the PR description that verification.failed should be emitted when the badge is missing; consider emitting it here as well (without blocking).
| self._auto_emit(EventEmitter.EVENT_VERIFICATION_FAILED, { | ||
| "method": request.method, | ||
| "path": path, | ||
| "reason": "missing_badge", | ||
| }) |
There was a problem hiding this comment.
The verification.failed auto-event emitted for a blocked missing-badge request does not include duration_ms, but the PR description lists duration_ms as a key field for verification failures. Consider including a duration (e.g., from request_start).
| the middleware automatically emits request.received, request.completed, | ||
| verification.success, and verification.failed events. |
There was a problem hiding this comment.
Docstring says the middleware "automatically emits request.completed" when an emitter is provided, but blocked requests (401/403 paths) return before _auto_emit_completed is called. Update the docstring to clarify when request.completed is (and is not) emitted, or emit it for blocked responses too.
| the middleware automatically emits request.received, request.completed, | |
| verification.success, and verification.failed events. | |
| the middleware automatically emits request.received, verification.success, | |
| and verification.failed events, and emits request.completed for requests | |
| that reach the downstream ASGI app (blocked 4xx responses may not emit | |
| request.completed). |
| # Middleware auto-event types (emitted automatically by CapiscioMiddleware) | ||
| EVENT_REQUEST_RECEIVED = "request.received" | ||
| EVENT_REQUEST_COMPLETED = "request.completed" | ||
| EVENT_REQUEST_FAILED = "request.failed" |
There was a problem hiding this comment.
EVENT_REQUEST_FAILED is introduced but not referenced anywhere in the repository. If it’s not part of the current auto-event contract, consider removing it to avoid confusing API consumers, or wire it into the middleware and add coverage.
| EVENT_REQUEST_FAILED = "request.failed" |
- CHANGELOG.md: document middleware auto-events feature - README.md: add middleware observability section - docs/guides/configuration.md: add auto-events configuration guide - docs/api-reference.md: add EventEmitter and event constants
|
✅ Documentation validation passed!
|
|
✅ All checks passed! Ready for review. |
|
✅ SDK server contract tests passed (test_server_integration.py). Cross-product scenarios are validated in capiscio-e2e-tests. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Summary
Adds opt-in automatic event emission to the FastAPI/Starlette middleware, closing the gap where all observability events had to be manually emitted.
Problem
The middleware handled badge verification but emitted zero events. Developers had to manually instrument every request lifecycle event, which meant most deployments had no observability into request patterns, verification outcomes, or latency.
Solution
When an
EventEmitteris provided toCapiscioMiddleware, it automatically emits standardized events at key middleware lifecycle points:request.receivedverification.successverification.failedrequest.completedDesign Principles
exclude_pathsemit nothing (no noise from health checks).Usage
New Event Type Constants
Added to
capiscio_sdk/events.py:EVENT_REQUEST_RECEIVEDEVENT_REQUEST_COMPLETEDEVENT_REQUEST_FAILEDEVENT_VERIFICATION_SUCCESSEVENT_VERIFICATION_FAILEDTests
7 new tests in
TestAutoEventscovering:All 50 unit tests pass (22 fastapi + 28 events).