Backend runtime
The backend runtime is a single Go binary (cmd/uwscrape-server).
On the Fly.io deployment it is the only server process; the static
frontend is embedded into the same binary via the embed_frontend
build tag.
Startup
A 16-step fail-closed startup sequence (constraint C-021 in the project intel) checks:
- Required config keys present:
UWSCRAPE_BIND_ADDR,UWSCRAPE_INDEX_DIR,UWSCRAPE_STATE_DB_PATH,UWSCRAPE_TOKEN_KEY_PATH. - Published index directory exists and contains all five required files.
release-decision.jsonisapprovedorapproved_with_warnings(refusesrejectedor missing decisions).- Index schema version is one the runtime supports.
- State SQLite opens in WAL mode with the configured busy timeout.
- Token verifier key file is readable and the right length.
Any failure emits an explicit error and refuses to serve traffic.
Endpoint surface
All public endpoints live under /api/v1. Roughly:
- Catalog —
GET /api/v1/courses,/courses/{subject}/{catalog_number},/credentials,/credentials/{credential_id},/source-references/{id}. (Catalog API) - Queries —
POST /api/v1/query/course-unlock,course-impact,credential-progress,credential-gap-summary,what-if,advisory. (Queries API) - Graph views —
POST /api/v1/graph/views/course-neighborhood,course-universe,course-pathways,credential-requirements,unlock-overlay,target-relevance,expand-node. (Graph views API) - State —
POST /state,GET/PATCH/PUT/DELETE /state/current,/state/current/export,/state/current/migration-preview. (State API) - Health & diagnostics —
GET /api/v1/health,/diagnostics,/index.
The internal-only GET /internal/metrics endpoint (gated by an
UWSCRAPE_INTERNAL_TOKEN bearer) is operator-only and is not part of
the public contract.
Common envelope
Every successful JSON response uses the shape:
{ "data": { ... }, "meta": { ... }, "warnings": [], "unknowns": [], "source_references": []}Error responses use error instead of data. State-bearing responses
include Cache-Control: no-store. RFC 9110 method semantics and
RFC 9111 caching apply. (ADR 0012,
Envelope API)
Academic engine
The runtime serves academic answers through the direct evaluator —
the only v1 engine. The direct evaluator handles all_of, any_of,
choose, completion, grade thresholds, unit counts, progress, standing,
program enrollment, antirequisite checks (when indexed), cross-listing
facts (when indexed), and opaque source text (as conservative unknown).
engine_unknown, engine_timeout, or engine_unsat from any future
engine never appears in the response as an academic status — those are
mapped through ADR 0018
with unknown_reason / conflict_reason codes. The public explanation
contract never exposes raw solver clauses, internal variable names, or
engine debug logs.
See Decision: Direct evaluator first and Decision: Multi-engine layer.
Bounded responses
Graph views and Advisory enumerations are bounded at the response layer. Defaults and hard maxima:
| Bound | Default | Hard max |
|---|---|---|
max_nodes | 250 | 2,500 |
max_edges | 600 | 7,500 |
max_depth | 2 | 4 |
max_courses | 100 | 2,500 |
max_credentials | 5 | 25 |
Exceeding defaults returns the result with graph_view_truncated in
warnings (status 200 OK). Exceeding hard maxima returns 400 bad_request.
(Graph view response spec)
Concurrency posture
The runtime serves ~50–200 concurrent users on a single Fly.io machine
(P10 target). State writes serialize through a WriteCoordinator queue
to avoid SQLite contention; reads are parallel and lock-free thanks to
WAL mode. (ADR 0027)
Want the full spec?
- Backend API spec — every endpoint, request, response.
- Backend runtime operations spec — config, startup, observability.
- Decision: Go runtime, read-only index, separate state store.
- Decision: REST + typed graph view endpoints — why no GraphQL in v1.