ADR 0017: Pluggable ASGI Middleware Stack
Status
Accepted
Context
The Framework M web layer is built on Litestar and uses ASGI middleware for cross-cutting concerns: authentication, rate limiting, CSRF protection, locale resolution, metrics, and now recent-document tracking for the Desk UI.
Previously, all middleware was hardcoded in framework_m_standard.adapters.web.app:create_app(). Adding a new middleware required editing the central application factory, which:
- Violates the "replaceable batteries" design goal.
- Creates merge conflicts when multiple apps or plugins contribute middleware.
- Makes it impossible for a deployment to disable or replace a default middleware without forking code.
Decision
We will manage the middleware stack through a singleton MiddlewareRegistry with the following properties:
- Priority-based ordering: Each middleware declares a numeric
priority; lower values run first (outer layer). - Entry-point discovery: Any package can contribute middleware by declaring it under
framework_m.middlewareinpyproject.toml. - Name-based overrides: Entry points with the same name override earlier registrations (last-wins).
- Config-driven overrides:
framework_config.tomlcan enable/disable, re-prioritize, or pass extra kwargs to any registered middleware. - Built-in registration in
create_app(): Middlewares that need runtime-constructed dependencies are still registered programmatically, but they participate in the same priority-ordered stack.
Consequences
Positive
- New middleware can be added without touching
app.py. - Apps and MX packages can replace default middlewares cleanly.
- Middleware order is explicit and configurable.
- Deployments can disable features like recent-document tracking via config.
Negative
- The registry is a singleton, so tests must clear it to remain deterministic.
- Built-in middlewares with runtime dependencies still require code changes in
create_app(); they cannot yet be fully declarative. - Entry-point discovery depends on package installation, which can be surprising in editable-development setups if the package metadata is stale.
Alternatives considered
Keep hardcoding middleware in app.py
Rejected because it centralizes change and conflicts with the plugin architecture.
Register each middleware in a bootstrap step
Rejected because it is still a one-off registration per middleware. Entry points are more declarative and align with the existing adapter-override pattern.
Use Litestar's built-in middleware config only
Rejected because Litestar's configuration does not support package discovery or deployment overrides through framework_config.toml.
Implementation notes
MiddlewareRegistrylives inframework_m_standard.adapters.web.middleware_registry.create_middleware_stack()is the single source of truth for building the Litestar middleware list.RecentDocumentMiddlewareis the first middleware contributed through the new entry-point group.