Applications
The mountable application classes: OpenAPI, MCP, and the automatic _server
application with its sections.
OpenAPI
OpenApiApplication: a RoutedApplication that exposes REST + OpenAPI + docs.
OpenApiApplication wraps an API surface — either the app’s own
@route methods (direct mode) or an external RoutingClass attached
under api_name (mounted mode) — and adds a _meta sub-tree with three
introspection endpoints:
_meta/schema_json— the OpenAPI 3.1 document of the API;_meta/docs— a Swagger-UI page pointing at_meta/schema_json;_meta/index— an HTML splash linking to the docs.
The schema is built STANDALONE from the app’s own router
(router_openapi(app.route)): there is no dependency on a _server
application (that surface belongs to a later macro). The mounted routing
class is linked as an eager instance branch, so it inherits the app
router’s plugins — pydantic among them — and its handler signatures are
captured into the neutral params/result blocks the
OpenAPITranslator reads; in direct mode the same plugins reach the app’s
own router through the server’s plugin arming (PluginMixin config).
The docs and splash HTML live in dedicated resource files next to this module and are read at USE time (never at import): a swap of the template file takes effect without re-importing the package.
Kwargs peeled by the cooperative __init__ (D16): routing_class (a
RoutingClass instance to mount), module ("pkg.mod:ClassName"
import path, an alternative to routing_class), docs (documentation
style — "swagger" or "off") and api_name (the segment the mounted
class is attached under, default "api"). The rest flows down the chain
(db_name to RoutedApplication, mount_name to BaseApplication).
- class genro_asgi.applications.openapi.OpenApiApplication(**kwargs)[source]
Bases:
RoutedApplicationExpose an API surface as REST + OpenAPI 3.1 with a Swagger docs page.
Two ways to supply the API:
direct mode — subclass and write
@routemethods on the app itself; endpoints sit at the app root (/{app}/endpoint);mounted mode — pass
routing_class=(ormodule=); the class is attached underapi_name(/{app}/{api_name}/endpoint).
Meta endpoints are attached under
_metain both modes.- Parameters:
kwargs (Any)
- property api_info: dict[str, Any]
the mounted class’s, else the app’s, else empty.
- Type:
OpenAPI info dict
MCP
MCP applications: the stateless Streamable HTTP transport over McpEngine.
Two ready-to-mount apps expose a genro-routes router as MCP tools over JSON-RPC
2.0. Both delegate the HTTP shell to a McpTransport — the helper that
owns everything the transport-agnostic McpEngine does not:
method/header/Origin gating, the JSON-RPC envelope, the 202-for-notifications
rule, and the sync/async invoke callback (async handlers stay on the loop, sync
handlers go through the server pool via run_sync — the Macro 1 protocol,
replacing the old smartasync). The transport holds its owning application as
self.application (dual-parent) and reaches its spread_over_params and
server through it.
McpApplication— the whole app is one MCP endpoint. It holds an engine over an EXTERNAL router (routing_class=ormodule=); every request is a JSON-RPC message. Without a routerinitializestill answers andtools/listis empty.McpOpenApiApplication— one router, two faces: it inherits the whole OpenApiApplication machinery (_metadocs, pydantic plug, REST dispatch) and adds an MCP face undermcp_name_segment(default"mcp"). Thechannelplugin drives per-face visibility: a method is an MCP tool only on channel"mcp"(@route(channel_channels="mcp,rest")for a dual method); undeclared methods default to REST-only (channels=rest_channel).
Transport conformance (MCP Streamable HTTP): a JSON-RPC POST answers with a
JSON response; a notification (no id) answers HTTP 202 with an empty body; a
GET opens the SSE push stream (below); any other method answers 405; an
MCP-Protocol-Version header that is present but unsupported answers 400 (an
absent header is assumed 2025-03-26 per the spec’s backwards-compat rule);
an Origin header present and not in allowed_origins answers 403 (the
allowed_origins option defaults to None — no restriction, a dev-mode
default: production fronting owns the Origin gate). The transport gates raise
core HTTP exceptions answered by the server’s ErrorMiddleware.
The push half (core 1e, the option-B commitment honored): a GET opens a
text/event-stream keyed by Mcp-Session-Id — echoed when the client
supplies one, minted otherwise (secrets.token_urlsafe, decoupled from the
cookie session: MCP clients carry no cookie) — and follows the server’s task
hub live (server.tasks.hub, the A<->C bridge). A Last-Event-ID header
replays the session’s current progress.json snapshots first
(snapshot-baseline resumability — no durable event log, ratified). A server
composed without tasks answers GET with 405, the 1c stateless behavior. The
engine stays untouched apart from advertising the capability.
- class genro_asgi.applications.mcp.McpApplication(**kwargs)[source]
Bases:
RoutedApplicationStandalone MCP transport: the whole app is one JSON-RPC endpoint.
Supply the tool surface as an external
RoutingClass(routing_class=instance, ormodule="pkg.mod:Class"imported and instantiated with no arguments); every route on it becomes a tool. Thechannelfilter only bites when the external router plugs thechannelplugin, so a plain router exposes all its routes. Without a router the app still answersinitializeand lists no tools. Class attributes carry the MCP identity defaults so a subclass can set them declaratively.- Parameters:
kwargs (Any)
- class genro_asgi.applications.mcp.McpOpenApiApplication(**kwargs)[source]
Bases:
OpenApiApplicationOpenApiApplication that also exposes its API router as MCP tools.
The REST/OpenAPI faces work exactly as in
OpenApiApplication; the MCP face answers undermcp_name_segment(default"mcp") via the same engine, pointed at the API router (the app’s own in direct mode, the mounted class’s in mounted mode). Visibility is thechannelplugin’s job: the MCP face lists only channel-"mcp"entries; undeclared methods default to REST-only (channels=rest_channel).- Parameters:
kwargs (Any)
- property mcp_engine: McpEngine | None
The MCP engine driving this app’s tool surface (
Noneif unbuilt).
- auth_filters(scope)[source]
Node-resolution filters: the base auth tags plus the REST channel.
The API router carries the
channelplugin (a method is an MCP tool only on channel"mcp"), so the REST face must resolve on the REST channel; the MCP face passes"mcp"through the engine. The filter is harmless when no router on the path plugschannel(it is ignored).
McpEngine — MCP (JSON-RPC 2.0) core over a genro-routes Router.
The engine turns a Router’s @route entries into MCP tools and serves the
protocol methods initialize / tools/list / tools/call. It is
transport- and app-agnostic: it holds a Router and a channel to filter on and
never touches HTTP concerns (headers, Origin, 202-for-notifications belong to
the host application). dispatch receives the parsed JSON-RPC message and
returns the RESULT object — envelope bookkeeping (id, jsonrpc) stays
with the transport; protocol failures raise McpError carrying the
JSON-RPC code for the transport to render. A list payload is rejected with
-32600: JSON-RPC batching entered the MCP spec in 2025-03-26 and was removed
in 2025-06-18.
Protocol (2025-11-25, the current revision):
initializenegotiates the version: the client’s requested version is echoed when it appears inSUPPORTED_VERSIONS, anything else is answered with the latest supported revision.tools/listwalksrouter.nodes(forbidden=False, channel_channel=...)so only the entries reachable on the engine’s channel are advertised. Tool names join the router path withtool_separator(default".", a character illegal in Python identifiers, sosub.ping<->sub/pinground-trips losslessly). Descriptors read ONLY the neutral blocks cached by genro-routes’ pydantic plugin at decoration time:inputSchemafrom the entry’sparams.schema(aggregaterequest_schema; fallback: an object schema assembled fromparams.fields),outputSchemafromresult.schema(response_schema). Nothing is derived from the callable and pydantic is never imported here.tools/callresolves the tool throughrouter.node(path, ...)and reads thenode.errorSTRING CODE (the stable genro-routes contract — resolution never raises):not_found/not_available-> -32601,not_authorized/not_authenticated-> -32000, any other code -> -32603. Execution is delegated to theinvokecallback so a host app can interpose parameter adaptation and pool dispatch; the default calls the node directly and the engine awaits an awaitable result (async handlers), nothing more. Input-validation failures are TOOL EXECUTION errors —{"isError": true, "content": [...]}results, not JSON-RPC protocol errors (SEP-1303, enables model self-correction). Validation runs INSIDE genro-routes (the pydantic plugin validates at call time; the engine never re-validates). Since genro-routes 0.28.0 every bad-argument error — apydantic.ValidationErroror an unbindable-argumentTypeErroralike — is channelled through the node’serrors={"validation_error": ...}seam to a local marker, so this module needs no pydantic import; the bareTypeErrorcatch remains for an async handler body raising at await time (a sync body’s TypeError is already folded into the marker upstream). Both becomeisErrorresults.pinganswers an empty result (spec MUST). A dict result is returned BOTH asstructuredContentand as its JSON text rendering (the unstructured content SHOULD match the declaredoutputSchema); any other result stays text-only.
- class genro_asgi.mcp.engine.McpEngine(router=None, *, name='genro-mcp', version='1.0.0', tool_separator='.', channel='mcp', invoke=None)[source]
Bases:
objectMCP JSON-RPC core over a router.
- Parameters:
router (
Router|None) – The genro-routes Router whose entries are exposed as tools.version (
str) – server identity returned byinitialize.tool_separator (
str) – joins router/method segments into a flat tool name.channel (
str) – channel to filter entries on (visibility per channel).invoke (
Callable[[Any,dict],Any] |None) – callback(node, arguments) -> resultrunning a resolved node; the engine awaits an awaitable result. Host applications pass their own to interpose parameter adaptation (e.g.spread_over_params) and pool dispatch for sync handlers; the default calls the node directly.name (str)
- async dispatch(payload, auth_tags=None)[source]
Route a parsed JSON-RPC message to its MCP handler.
Returns the JSON-RPC RESULT object; the transport owns the envelope.
- Raises:
McpError – invalid message shape (-32600, batching included) or unknown method (-32601);
tools/callresolution failures bubble up fromhandle_tools_call().- Return type:
- Parameters:
- handle_initialize(params)[source]
Negotiate the protocol version and return the server capabilities.
The client’s requested version is echoed when supported; any other request is answered with the latest supported revision (spec negotiation rule).
- handle_tools_list()[source]
Enumerate the tools visible on this channel.
forbidden=Falseexcludes entries the channel does not expose, so the tool list carries only what is reachable on this channel.- Return type:
- async handle_tools_call(params, auth_tags=None)[source]
Resolve a tool name to its router node, invoke it, wrap the result.
Bad tool arguments come back as
isErrorresults — genro-routes 0.28.0 folds validation failures AND unbindable arguments into thevalidation_errormapping, while theTypeErrorcatch covers an async handler body raising at await time; resolution failures readnode.errorand raiseMcpError.
Server application
ServerApplication: the automatic _server system app (D4).
ServerApplication is the server’s own application — the system surface
every server exposes under /_server without configuring it (D4:
“automatic, not configured”). AsgiServer mounts one at the end of its
__init__ (_mount_server_app), so a hand-built
AsgiServer(primary=...) gets it exactly like a config-materialized one;
ConfigurationHandler.materialize never special-cases it. The demux finds
it through the ordinary mount table — there is no dedicated demux logic.
It extends OpenApiApplication (REST + OpenAPI; the MCP face on
_server is out of this wave), so /_server/_meta/ carries the usual
schema/docs/index endpoints, and adds:
index— the/_server/descriptor: title and the attached section names (JSON — no HTML in code);sections/attach_section(section, name)— the registry of system sections:attach_sectionlinks aRoutingClassundername(endpoints at/_server/<name>/...) and records it so introspection surfaces (the index today, monitors later) can enumerate them;the PASSWORD login surface (core 1d wave 1):
login(JSON POST →UserStore.verify→Avatar→request.session.attach_avatar),login_page(HTML GET, the descriptor-drivenresources/login.htmlread at USE time),logoutand the publiclogin_methods— dual-mode by TWO routes, never in-handlerAcceptsniffing. The methods live in anAuthSectionattached underauth(ensure_auth_section/register_auth_method);PasswordMethodis registered at construction.loginenforces the store-backed lockout (REVIEW #9): the per-identity failure counter (failed_attempts/last_failed_at) rides the UserStore record with exponential backoff; the policy comes from the config’slogin()element (login_policy, defaults 5 attempts / 30s base).
Handlers stay PURE: they return values and never touch cookies or an ambient
request/response (the old self.server.request idiom must never be
reintroduced). Login attaches the avatar to the existing session in place —
the id never changes, so no login-time cookie exists. A handler that needs the
live request DECLARES an UNANNOTATED _request parameter: bind_kwargs
injects the per-dispatch Request for that name — the same declarative
convention body_data follows — and the handler reaches the server through
_request.server. Leaving it unannotated keeps it out of the pydantic model
(and thus the public OpenAPI schema); the pydantic wrapper, seeing no type hint,
passes it straight through instead of routing it into validation. The _
prefix is the injected-name convention bind_kwargs matches in the neutral
fields block. pydantic and openapi are fixed server structure (armed
on every router by PluginMixin), so the handler signatures are always
captured and per-entry OpenAPI controls (openapi_method) always take effect.
The future internal server (a D8 orchestration concern) is a SUBCLASS that overrides what it needs — not a profile flag on this class: no code exists for a consumer that does not exist yet.
Kwargs peeled by the cooperative __init__ (D16): mount_name is FIXED
to "_server" — the system mount is a D4 invariant, not a preference, and
three cross-file references hardcode /_server/... (PasswordMethod’s
action, LOGIN_PAGE_URL, login.html’s fetch). A non-default value
raises rather than silently 404-ing those references. login and oidc
are the values the config-class lift carries (the server_app= server kwarg,
forwarded by _mount_server_app): the lockout policy dict and the
per-code OIDC provider dicts, stored as login_policy/oidc_providers
(consumed by the lockout check and the OidcMethod registration). The rest
flows down the chain. A hand-built AsgiServer(primary=...) passes nothing,
so the defaults (empty dicts) keep today’s bare app.
- class genro_asgi.applications.server_app.ServerAppConfig(name=None)[source]
Bases:
BuilderBase,ServerAppConfigElementsThe
_serverapp’s config-class: the base IS the default.main()declares nothing, so materializing the base yields today’s bareServerApplication()— no configuration, no regression. A site personalizes_serverby subclassing this in itsconfig.pyand passing the subclass toConfigurationHandleralongside the site recipe; the handler claims it by class identity (ServerApplication.config_class), no name registry.- Parameters:
name (str | None)
- class genro_asgi.applications.server_app.ServerAppConfigElements[source]
Bases:
objectConfig grammar of
ServerApplication— the dialect of its config-class.NOT composed into the site dialect: an application’s configuration is a SEPARATE builder (distributed config), and
_server’s is the one the core knows how to mount by construction. Declares the identity surface:admin_password(a^pointernode value) plususers()/tokens()store descriptors. All three are LIFTED to the SERVER constructor kwargs (AuthMixinpeels them): the stores live on the server (Phase 3) — the config keeps the app’s shape, the runtime stays where it is. The login surface (login()policy,oidc()providers) lifts as the singleserver_app=server kwarg instead: those values belong to THIS app, which peels them whenAsgiServerforwards them at mount time.- admin_password = <genro_builders.builder._decorators._DeclarativeMarker object>
- users = <genro_builders.builder._decorators._DeclarativeMarker object>
- tokens = <genro_builders.builder._decorators._DeclarativeMarker object>
- login = <genro_builders.builder._decorators._DeclarativeMarker object>
- oidc = <genro_builders.builder._decorators._DeclarativeMarker object>
- class genro_asgi.applications.server_app.ServerApplication(**kwargs)[source]
Bases:
OpenApiApplicationSystem endpoints of a server, auto-mounted under
/_server(D4).Carries the public server’s system surface: the password login surface and the sections attached through
attach_section, listed by theindexdescriptor. The future internal server (a D8 orchestration concern) will be a SUBCLASS overriding what it needs — not a profile flag on this class.- Parameters:
kwargs (Any)
- config_class
alias of
ServerAppConfig
- property login_policy: dict[str, Any]
The lockout policy from the config’s
login()element (may be empty).
- property oidc_providers: dict[str, dict[str, Any]]
OIDC provider configs from the
oidc()elements, keyed by code.
- property sections: dict[str, RoutingClass]
Attached system sections keyed by their mount segment (may be empty).
- property auth_section: AuthSection | None
The
authsection carrying the login methods, orNone.
- attach_section(section, name)[source]
Attach
sectionundernameand record it insections.Links the section’s router into this app (endpoints at
/_server/<name>/...) and keeps it enumerable for the introspection surfaces (theindexdescriptor today).
- register_auth_method(method)[source]
Register a login method in the
authsection (created on demand).- Return type:
- Parameters:
method (AuthMethod)
- bind_kwargs(node, request)[source]
Inject the live
Requestinto handlers that declare_request.Extends the base reconciliation with the declarative seam the login surface needs: when the node’s neutral
paramsblock declares a_requestparameter, the per-dispatchRequestis bound to it (overriding any same-named wire value). Handlers leave_requestunannotated so it stays out of the pydantic model — and therefore out of the public OpenAPI schema — while still being visible in the neutralfieldsthis method reads. No ambient state — the request travels as an ordinary argument, exactly likebody_data.
- login(identity='', password='', _request=None)[source]
Authenticate against the server’s UserStore and attach the identity.
The JSON convergence point of every
formmethod: verifies the credentials (UserStore.verify— the record key isidentity), builds theAvatarand attaches it to the request’s session in place (_request.session.attach_avatar) — the session id never changes at login, so the client’s cookie stays valid and noSet-Cookieis involved. The server’suser_storeis wired in the next wave (Macro 5b): until then a server without one answers the error shape.The
nextreturn path is NOT a login parameter: the challenge redirects tologin_page?next=...and the page script owns the post-success redirect —loginitself never sees it and posts carry only the credentials.Enforces the server-side lockout (REVIEW #9): the failure counter lives ON the user’s store record (
failed_attempts/last_failed_at), so it survives restarts and is shared across processes on a shared store. Aftermax_attemptsconsecutive failures the identity is refused until the exponential-backoff window (_lock_seconds_remaining) has passed; refused attempts never touch the counter — an attacker hammering a locked identity cannot extend a legitimate user’s lock — and a success resets it. Known-identity failures surface the server-computedremaining_attempts; unknown identities have no record, hence no counter and no such field. Per-IP rate limiting is a future middleware concern, not this handler’s.The method is POST by declaration (
openapi_method="post"): with_requesthidden from the schema (see below) the remaining fields are all scalar, so the guesser would otherwise pick GET.- Parameters:
identity (
str) – The record key to verify (NOT the oldusername).password (
str) – The password to verify._request – The live
Request, injected bybind_kwargs. Left unannotated so it stays out of the pydantic model — and thus out of the public OpenAPI request body — while the_prefix is the injected-name conventionbind_kwargsmatches.
- Return type:
- Returns:
{session_id, identity, tags}on success;{"error": ...}on missing/invalid credentials, active lockout, or when no user store is wired — withremaining_attemptswhen the identity has a record.
Note
Route: POST /_server/login
- login_page(next='')[source]
Serve the descriptor-driven HTML login page (GET, dual-mode twin of
login).The page builds itself from
login_methodsand posts credentials to the method’saction(/_server/login). Read at USE time so a template swap needs no re-import.nextis accepted so the challenge redirect’s query binds; the page script consumes it client-side.Note
Route: GET /_server/login_page
- logout(session_id='')[source]
Destroy a session.
Deletes the session from the store. No error if the session is unknown.
- Parameters:
session_id (
str) – Session token to invalidate.- Return type:
- Returns:
{"status": "ok"}(always succeeds).
Note
Route: POST /_server/logout
- login_methods()[source]
Public descriptors of the active auth methods (NO
auth_rule).The login page builds itself from this: register a method, its descriptor (and therefore its button/form) appears. Deliberately public — a caller must see the methods before it can authenticate. Empty list when no login surface is active.
Note
Route: GET /_server/login_methods
Server sections
The _server/auth container: the mount that holds the auth-method sections.
When the login surface is active the ServerApplication attaches ONE
AuthSection under the auth name, so it lives at /_server/auth/.
A registered auth method (AuthMethod) is attached to this section under
its method_id ONLY when it owns routes, so those routes live at
/_server/auth/<method_id>/ (e.g. a future OIDC start and callback
at /_server/auth/oidc:google/start). A route-less method — the password
one — is recorded in the registry but never attached: zero-route nodes never
enter the routing tree (Invariant 10).
The section is a thin router node: it holds no routes of its own, it only
carries the routed method children and keeps the ordered registry the login
surface reads to build login_methods. Routing is dispatch; the registry
is this dict.
- class genro_asgi.applications.server_sections.auth_section.AuthSection(application)[source]
Bases:
RoutingClassThe
_server/authmount that carries the registered auth methods.Note
Parent (dual relationship): the ServerApplication, stored as
self.application. The AsgiServer is reached viaself.application.server.- Parameters:
application (ServerApplication)
- __init__(application)[source]
Bind the section to its ServerApplication and start an empty registry.
- Parameters:
application (
ServerApplication) – The ServerApplication this section belongs to (dual relationship). The AsgiServer isapplication.server.- Return type:
None
- property methods: dict[str, AuthMethod]
The registered methods, keyed by
method_id(insertion order).
- register(method)[source]
Record a method; mount its routes only when it owns some.
Every method enters the ordered registry the login surface reads to build
login_methods. Only a method that OWNS routes is also linked into this section’s router undermethod_id(so its routes live at/_server/auth/<method_id>/); a route-less method (the password one) stays registry-only — zero-route nodes are never attached to the routing tree (Invariant 10: routing is dispatch, never a registry).- Parameters:
method (
AuthMethod) – The AuthMethod to register. Itsmethod_idmust be unique.- Raises:
ValueError – If a method with the same
method_idis already registered (method ids are unique by contract, so a clash is a configuration bug).- Return type:
The _server/users section: SUPERADMIN-gated user management.
UsersSection is a RoutingClass the ServerApplication attaches under
users (endpoints at /_server/users/...). Every route is gated
auth_rule="SUPERADMIN" — the section is ALWAYS declared (fixed structure,
D26), and each handler answers the {"error": ...} shape (coherent with
login) when the server has no user_store wired.
The credential invariant: password_hash NEVER crosses the wire. list
and get strip it from the record; save never accepts it (it merges the
non-credential fields of the body over the stored record, preserving the hash);
a password enters the system only as plaintext through create_user and
set_password, hashed server-side via UserStore.hash_password.
Route responsibilities are separated:
create_user— births a NEW record (error if the identity exists): the body carriespassword/password_confirm(the server checks they match) plus the metadata andtags;save— updates an EXISTING record only (error if absent): merges the body’s metadata/tags over the stored record,password_hashuntouched. The body is taken whole (body_data) so tomorrow’s metadata fields need no signature change;set_password— changes the credential of an existing record only, with the samepassword/password_confirmserver-side check;delete— removes a record.
Parent (dual relationship): the ServerApplication, stored as
self.application; the store is reached via self.application.server.user_store.
- class genro_asgi.applications.server_sections.users_section.UsersSection(application)[source]
Bases:
RoutingClassThe
_server/usersmount: SUPERADMIN CRUD over the server’s UserStore.Note
Parent (dual relationship): the ServerApplication, stored as
self.application. The store isself.application.server.user_store.- Parameters:
application (ServerApplication)
- __init__(application)[source]
Bind the section to its ServerApplication (dual relationship).
- Parameters:
application (ServerApplication)
- Return type:
None
- property user_store: UserStore | None
The server’s UserStore, or
Nonewhen identity is unconfigured.
- matched_password(body)[source]
The password when it is present and matches its confirmation, else None.
The caller distinguishes a mismatch (this returns None) from an absent password by checking the body itself: only
create_userrequires one.
- create_user(identity='', body_data=None)[source]
Create a NEW user from the body (metadata + tags + password/confirm).
Errors if the identity already exists. The password is validated against its confirmation and hashed server-side;
password_hashnever arrives pre-formed. Metadata andtagsfrom the body land on the new record. A new record defaults toenabled: Trueandtags: [](creating a user with a password means letting them log in — the body can still sayenabled: falseto create it disabled);verifyrequiresenabledand the loginAvatarrequirestags, so a minimal body must still produce a working user.
- save(identity='', body_data=None)[source]
Update an EXISTING record’s metadata/tags;
password_hashuntouched.Errors if the user does not exist (creation is
create_user’s job). The body is merged whole over the stored record — new metadata fields persist with no signature change — but the credential never moves here.
- set_password(identity='', body_data=None)[source]
Change an existing user’s password (plaintext in, hashed server-side).
The body carries
password/password_confirm; the server checks they match. Errors if the user does not exist.
The _server/tokens section: SUPERADMIN-gated issued credentials.
TokensSection is the ONE section for credentials the server issues: API
keys (gak_) and short-lived JWTs. It is a RoutingClass the
ServerApplication attaches under tokens (endpoints at
/_server/tokens/...), ALWAYS declared (fixed structure, D26). Every route is
auth_rule="SUPERADMIN" and answers the {"error": ...} shape when the
server has no api_key_store wired.
The secret invariant mirrors the users section: an api key’s secret_hash
NEVER crosses the wire (list strips it), and the full gak_ key is
returned ONLY by issue, once — it is never retrievable again (the record
keeps only its hash).
create_jwt mints a JWT signed with the FIRST symmetric (HS*) verifier in
the auth config (AuthCore.signing_jwt_config): the token verifies against
the same config, so no new key material is introduced. With no symmetric verifier
configured it answers the error shape.
Parent (dual relationship): the ServerApplication, stored as
self.application; the stores are reached via self.application.server.
- class genro_asgi.applications.server_sections.tokens_section.TokensSection(application)[source]
Bases:
RoutingClassThe
_server/tokensmount: SUPERADMIN api-key registry + JWT minting.Note
Parent (dual relationship): the ServerApplication, stored as
self.application. The stores live onself.application.server.- Parameters:
application (ServerApplication)
- __init__(application)[source]
Bind the section to its ServerApplication (dual relationship).
- Parameters:
application (ServerApplication)
- Return type:
None
- property api_key_store: ApiKeyStore | None
The server’s ApiKeyStore, or
Nonewhen tokens are unconfigured.
- issue(body_data=None)[source]
Mint an api key; return the full
gak_key ONCE (never again).Body:
label(required),tags(default[]),expires_at(POSIX timestamp or absent for a key that never expires).
- create_jwt(body_data=None)[source]
Mint a JWT signed with the first symmetric verifier of the auth config.
Body:
sub(required — the token’s subject),tags(default[]),expires_in(seconds; absent for noexpclaim). The token verifies against the same config it was signed with (no new key material). Answers the error shape when no symmetric verifier is configured.
The _server/tasks section: SUPERADMIN-gated task backbone endpoints.
A RoutingClass the ServerApplication attaches (attach_section), so
its routes live at /_server/tasks/.... JSON endpoints ONLY — no HTML/JS
panel ships with the core (ratified). Two surfaces over the server’s
TaskManager:
schedules (the recurring scheduler’s store):
list/create/update/enable/disable/run_now/delete/logs;spool (the batch folders):
spool_list(byownerorstatus),progress,cancel,result.
Every route is gated auth_rule="SUPERADMIN". A server composed without the
task backbone — or with tasks=False — answers every endpoint with the
{"error": ...} document (HTTP 200): the section is ALWAYS declared, fixed
structure (D26), the payload states the availability.
Parent (dual relationship): the section holds its ServerApplication as
self.application and reaches the manager via self.application.server.tasks
(guarded by tasks_enabled — the property raises when disabled).
- class genro_asgi.applications.server_sections.tasks_section.TasksSection(application)[source]
Bases:
RoutingClassThe
/_server/tasksendpoints over the server’s task backbone.Note
Bound to its
ServerApplication(dual relationship:self.application); the manager, store, scheduler and spool are reached throughself.application.server.tasks.- Parameters:
application (ServerApplication)
- __init__(application)[source]
Bind the section to its ServerApplication (dual relationship).
- Parameters:
application (ServerApplication)
- Return type:
None
- property manager: TaskManager | None
The server’s TaskManager, or
Nonewhen tasks are off/absent.
- create(body_data=None)[source]
Create a schedule:
code,kind,specrequired.task_namedefaults tocode;kwargsandenabledare optional.next_run_tsis computed here (an invalid spec is the{"error": ...}answer, not a record).
- update(body_data=None)[source]
Merge the editable fields into a schedule (
codenames it).A changed
kind/specrecomputesnext_run_ts; the run-outcome fields are the scheduler’s and never settable from the wire.
- spool_list(owner='', status='')[source]
Task descriptors by
ownerOR bystatus(one filter required).