What is absent, and what to do instead¶
Status: Phase 8. Every row below is a decision with a reason, not an unfinished edge. This page is the index from a gap to its answer, so a reader who notices something missing finds the seam, the package, or the recipe that closes it without leaving the framework for a batteries-included one.
The placement rule is ADR 0018: a capability is an extra
here, a module here, a package there, or a refusal. The row that matters most is the
third, because "there" is the answer to "do I have to adopt Django for this?" — no, the capability
arrives as a small distribution that builds on a contract this line already ships, and pip install
xyberos never pulls it in.
The four gaps a reader notices first¶
Each has a recorded reason and a documented way through. None of them requires a framework.
Identity and authorization — verify an assertion, pass the principal¶
Absent by decision (ADR 0014): authorization is a product decision, and a framework that guesses owns security it cannot verify.
What to do instead: verify the assertion upstream, then pass the principal as an argument.
xyberos-auth does the verifying half and stops; the scoping half is a service signature.
from xyberos_auth import AuthSettings, PrincipalVerifier
verifier = PrincipalVerifier(AuthSettings(audience="checkout", secret=os.environ["AUTH_SECRET"]))
@app.get("/orders")
async def list_orders(request: Request) -> dict[str, object]:
principal = verifier.verify(request.headers["Authorization"].removeprefix("Bearer "))
return {"orders": await orders.for_tenant(principal.tenant)} # the tenant is an argument
pip install xyberos-auth — verification only, shared secret or JWKS. The tested shape, including the
case that matters most, is the tenancy example.
Sessions — a middleware here, the store there¶
Shipped as middleware (xyberos.http.SessionMiddleware, ADR 0024),
because the store is the deployment's decision and the mechanism is not:
from starlette.middleware import Middleware
from xyberos.http import HttpAdapter, InMemorySessionStore, SessionMiddleware
store = InMemorySessionStore() # swap for a package beside this line in production
adapter = HttpAdapter(
app,
middleware=(Middleware(SessionMiddleware, store=store, secret=os.environ["SESSION_SECRET"]),),
)
@app.get("/me")
async def me(request: Request) -> dict[str, object]:
return {"user": request.session.get("user")} # Starlette's own spelling
The cookie is a signed session id, not the data, so a handler's values never reach the client; the
session is written only when a handler changed it; and a durable store is a package there, reached
through the SessionStore protocol. The recipe is the sessions example.
CSRF — a middleware here, because there is nothing to install¶
Shipped as middleware (xyberos.http.CSRFMiddleware). Starlette ships sessions but no CSRF
middleware, so unlike sessions it is not a marshalling of an existing dependency:
adapter = HttpAdapter(app, middleware=(Middleware(CSRFMiddleware),))
@app.get("/order-form")
async def order_form(request: Request) -> dict[str, str]:
return {"csrf_token": get_csrf_token(request)} # one line, and a page can embed it
A state-changing request whose Origin (or Referer) names another site is refused with a 403; a
presented X-CSRF-Token that does not match the issued one is refused too. A request with neither is
not a browser cross-site request and is allowed, which is why curl still works. Set
trusted_origins behind a proxy.
A second database — write a provider, prove it with a suite¶
Absent by decision (ADR 0016): SQLite ships, and the seam is any async SQLAlchemy dialect rather than "any database".
What to do instead: pip install xyberos-postgres (published, 0.1.0), or implement
DatabaseProvider — three members over an AsyncConnection — and prove it with one call:
from xyberos_postgres import PostgresSettings, postgres_provider
from xyberos.database import DatabaseProvider
app.provide(
DatabaseProvider,
factory=postgres_provider(PostgresSettings(host="db.internal", database="app")),
eager=True,
)
assert_provider_contract is the whole conformance requirement, and services do not change: they use
transaction(), retrying(), and health() either way. See
writing a database provider.
Everything else that is absent¶
| You notice | Placement | What to do instead |
|---|---|---|
| An ORM, models, repository base classes | Refused (positioning) | Use SQLAlchemy Core directly over the provider; xyberos-crud adds filtering, sorting, and pagination as values |
| An admin UI | Refused | Build the screens; xyberos-crud and xyberos-templates are the pieces |
| OpenAPI generation, an admin API explorer | Refused | Serialize app.diagnostics() and the route registry yourself (ADR 0020) |
| RAG, a vector store, an embeddings index | Refused | xyberos.ai has structured output and tools; retrieval is your design |
| Job queues, a scheduler | Refused | Durable execution is single-host (ADR 0015); use a queue as the delivery layer and keep the worker single-host per journal |
| Rate limiting, request size limits, TLS | Refused | They belong to the ASGI server and the reverse proxy (docs/security.md) |
| Feature flags | Refused | Configuration and plugins; nothing framework-owned |
| Multi-host durable execution, exactly-once external effects | Refused | Temporal, Argo, or a managed workflow service; this line runs the worker role well |
| An entry-point plugin registry | Refused (ADR 0007) | app.add_plugin(...) explicitly, so startup stays readable |
| Templates, static files, form reading | Package there | pip install xyberos-templates (Jinja2 shipped, render off the event loop, escaping stated) |
| Logging preset, tracing spans | Package there | pip install xyberos-observability |
| Exposing your own tools over MCP | Package there | pip install xyberos-mcp (expose(app, tools)); the client half is core xyberos.mcp |
| S3-compatible object storage | Package there | pip install xyberos-storage-s3, proved by assert_storage_contract |
| An LLM provider other than Ollama | Package there | pip install xyberos-openai-compatible, proved by assert_model_provider_contract |
| CRUD query construction | Package there | pip install xyberos-crud |
| Migrations | Already ships | xyberos[database]: run_migrations and apply_migrations drive Alembic |
| Request validation | Already ships | xyberos.ai.schema: validate_payload, check_schema, describe_schema |
| Request metrics | Already ships | xyberos.http.RequestMetricsMiddleware |
The retired names¶
Two 1.x names are retired rather than reused, so a pinned dependency keeps resolving:
xyberos-db points at xyberos[database] and the provider seam, and xyberos-metrics points at
xyberos.http.RequestMetricsMiddleware. Neither is re-released under the old name.
Adding one yourself¶
If the row you need is not here, ADR 0018 says where a new capability goes and what it costs. The short version: an extra is a promise the release makes, a module here must need nothing from PyPI, and anything else is a package there with an import root of its own. The plugin guide is the how-to.