Coverage Report

Created: 2026-09-05 23:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
super-stt-daemon/src/daemon/http/openapi.rs
Line
Count
Source
1
// SPDX-License-Identifier: GPL-3.0-only
2
//! The `OpenAPI` document for the daemon protocol.
3
//!
4
//! The document is generated from the router, not written beside it: every
5
//! `/v1` route is registered through [`utoipa_axum::routes!`], which reads the
6
//! `#[utoipa::path]` attribute on the handler it points at. A route and its
7
//! documentation are therefore one declaration — adding a route without
8
//! documenting it does not compile, and changing a path changes both.
9
//!
10
//! `just openapi` writes the result to `docs/protocol/openapi.json`;
11
//! `just openapi-check` fails when the committed file is stale, so a protocol
12
//! change cannot merge without the published spec moving with it.
13
//!
14
//! The prose reference under `docs/protocol/` is not replaced by any of this.
15
//! It explains *when* to call an endpoint and how the pieces fit; the spec
16
//! states the shapes exactly, for tooling and for a client generator.
17
18
use utoipa::Modify;
19
use utoipa::OpenApi;
20
use utoipa::openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme};
21
22
/// Base document: everything that is true of the protocol as a whole rather
23
/// than of one endpoint. The paths and schemas are filled in from the router
24
/// (see [`super::v1::openapi`]).
25
#[derive(OpenApi)]
26
#[openapi(
27
    info(
28
        title = "Super STT daemon protocol",
29
        description = "\
30
HTTP/1.1 + JSON over a Unix domain socket at `$XDG_RUNTIME_DIR/stt/super-stt-http.sock` \
31
(override with `SUPER_STT_HTTP_SOCKET`). There is no TCP listener: the socket's \
32
filesystem permissions are the first layer of access control, and the daemon reads \
33
`SO_PEERCRED` on each connection to identify the calling binary.
34
35
Every endpoint except `POST /v1/auth/request` requires `Authorization: Bearer <token>`. \
36
A token is minted only after the user approves your app in a consent popup, and is bound \
37
to the approved binary — an app cannot widen its own permissions. See `docs/protocol/auth.md`.
38
39
Because the transport is a Unix socket, the `servers` entry below is nominal; point your \
40
client at the socket and use any `Host`. With curl:
41
42
```
43
curl --unix-socket \"$XDG_RUNTIME_DIR/stt/super-stt-http.sock\" \\
44
     -H \"Authorization: Bearer $STT_TOKEN\" \\
45
     http://stt.local/v1/ping
46
```",
47
        license(name = "GPL-3.0-only", identifier = "GPL-3.0-only"),
48
        contact(name = "Super STT", url = "https://github.com/jorge-menjivar/super-stt"),
49
    ),
50
    servers((url = "http://stt.local", description = "Nominal host; the transport is the Unix socket")),
51
    modifiers(&BearerAuth),
52
    tags(
53
        (name = "auth", description = "Consent handshake and token probing."),
54
        (name = "health", description = "Liveness and what the daemon is currently running."),
55
        (name = "transcribe", description = "Start, stop and stream transcription."),
56
        (name = "events", description = "Server-Sent Events for recording state, audio levels, model and download progress, and final transcripts."),
57
        (name = "pipeline", description = "The ordered stages a transcript passes through: which backend fills each, which model runs there, and on what device."),
58
        (name = "settings", description = "Stored daemon preferences, one value apiece, all under `/v1/settings`: audio cues, write and notification methods, language, update policy. Sharing the `settings` scope is not the same as being a setting \u{2014} `backends`, `pipeline` and `registry` are guarded by it too."),
59
        (name = "hardware", description = "What the daemon can see of this machine: GPUs, drivers, runtimes."),
60
        (name = "update", description = "Whether a newer daemon exists, and asking it to look now."),
61
        (name = "backends", description = "Installed backends: their models, options and secrets."),
62
        (name = "registry", description = "The published backend catalog: browse, install, update, uninstall."),
63
    ),
64
)]
65
pub(crate) struct ApiDoc;
66
67
/// The one security scheme: the session token from `POST /v1/auth/request`,
68
/// presented as a bearer token. Declared here rather than per endpoint so the
69
/// scheme has a single definition; which *scopes* each endpoint needs is stated
70
/// on the endpoint, since that is where it differs.
71
struct BearerAuth;
72
73
impl Modify for BearerAuth {
74
14
    fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
75
14
        let components = openapi
76
14
            .components
77
14
            .as_mut()
78
14
            .expect("the derived document always carries a components object");
79
14
        components.add_security_scheme(
80
            "session_token",
81
14
            SecurityScheme::Http(
82
14
                HttpBuilder::new()
83
14
                    .scheme(HttpAuthScheme::Bearer)
84
14
                    .description(Some(
85
14
                        "Session token from `POST /v1/auth/request`. Bound to the calling \
86
14
                         binary and valid for 30 days.",
87
14
                    ))
88
14
                    .build(),
89
14
            ),
90
        );
91
14
    }
92
}