Skip to content

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.json is approved or approved_with_warnings (refuses rejected or 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:

  • CatalogGET /api/v1/courses, /courses/{subject}/{catalog_number}, /credentials, /credentials/{credential_id}, /source-references/{id}. (Catalog API)
  • QueriesPOST /api/v1/query/course-unlock, course-impact, credential-progress, credential-gap-summary, what-if, advisory. (Queries API)
  • Graph viewsPOST /api/v1/graph/views/course-neighborhood, course-universe, course-pathways, credential-requirements, unlock-overlay, target-relevance, expand-node. (Graph views API)
  • StatePOST /state, GET/PATCH/PUT/DELETE /state/current, /state/current/export, /state/current/migration-preview. (State API)
  • Health & diagnosticsGET /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:

BoundDefaultHard max
max_nodes2502,500
max_edges6007,500
max_depth24
max_courses1002,500
max_credentials525

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?