Skip to main content

Crate improv_server

Crate improv_server 

Source
Expand description

improv-server: a small JSON HTTP API over an Improv model store.

The (blocking SQLite-backed) Mentat store is opened inside spawn_blocking and never shared across tasks. The loaded model is cached in AppState and replaced on every write this process makes (API or scheduler), so reads cost one store load per write, not per request. The server assumes it owns the store: a write from another process is not seen until restart.

ModulesΒ§

scheduler πŸ”’
Hosted refresh scheduler: a background tokio task mirroring the CLI’s standalone improv serve-refresh daemon (crates/cli/src/main.rs, cmd_serve_refresh), but running in-process alongside the API instead of as a separate process.

StructsΒ§

AppState
Cell πŸ”’
EvalEdit πŸ”’
One edit in an /eval request: measure id, coord as [[cat_id, item_id], ...] (numeric ids, matching CoordKey), and value (null clears the cell).
EvalReq πŸ”’
MeasureSummary πŸ”’
NlDescribeReq πŸ”’
NlParseReq πŸ”’
ReadOnly πŸ”’
Request extension set by require_token for a read-only token.
SetCellReq πŸ”’

EnumsΒ§

ApiError πŸ”’
Auth
Accepted bearer tokens, or open mode.

ConstantsΒ§

HEADER_READ_TIMEOUT πŸ”’
Default deadline for a client to finish sending request headers (S10).
MAX_BODY_BYTES πŸ”’
Request body cap (S13). Over-limit bodies get 413.
MAX_EDITS πŸ”’
Edits accepted per /eval request (S13). More gets 400.
MAX_IN_FLIGHT πŸ”’
Requests in flight across all routes (S10); the rest wait (then time out).
REQUEST_TIMEOUT πŸ”’
Whole-request deadline, including time queued for a concurrency slot (S10).

StaticsΒ§

NEXT_ERROR_ID πŸ”’
Id logged with each internal error and echoed to the client as request_id.

FunctionsΒ§

app
Build the router. Exposed so tests can drive it via oneshot.
category_name πŸ”’
ct_eq πŸ”’
Byte-wise xor accumulate; no early exit on the first differing byte.
eval_measure πŸ”’
Incremental / what-if evaluation. Builds a short-lived live Engine over all derived measures, applies the batch of edits as deltas, and returns the recomputed snapshot for measure :id as [{coord: [[cat_name,item_name]..], value}].
get_model πŸ”’
health πŸ”’
list_measures πŸ”’
load_model πŸ”’
The current model: the cached copy, or (on a miss) one load from the store on a blocking thread. The cache write lock is held across the load, so a concurrent writer’s save_model replaces it only after a stale load lands.
main πŸ”’
measure_values πŸ”’
nl_describe πŸ”’
nl_parse πŸ”’
readable_cell πŸ”’
Render a coordinate as [[category_name, item_name], ...], sorted by category id for stability.
readable_key πŸ”’
require_token πŸ”’
Bearer-token gate. In open mode every request passes. Otherwise: missing/blank Authorization -> 401; present but not an accepted token -> 403; a read-only token passes with a ReadOnly extension the write paths refuse. The token value is never logged.
save_model πŸ”’
Save the model on a blocking thread and make it the cached copy. Call with write_lock held.
scheduler_status_handler πŸ”’
Scheduler observability: enabled/tick/last-refreshed. Protected (not a liveness probe like /health) since it can reveal which measures exist.
serve πŸ”’
Accept loop: hyper HTTP/1 with a header-read timeout (S10). axum::serve (0.7) sets no header timer, so a client that never finishes its request line holds a socket forever (slow-loris). HTTP/1 only: the hyper-util auto builder would pull in h2, which the API does not need.
set_cell πŸ”’
startup_check
Refuse open mode (no tokens) on a non-loopback address unless the operator opts in with IMPROV_ALLOW_OPEN=1 (S15).
token_matches πŸ”’
Constant-time token check (S15): every accepted token is compared, and each comparison folds over all bytes, so timing reveals neither which token matched nor the length of a matching prefix.
validate_write πŸ”’
S12: a write must target an existing input measure, at a coordinate naming exactly the measure’s categories, each with an item of that category, and carry a finite value (None clears the cell).