lex_api/lib.rs
1//! M8: agent API server. Spec §12.3.
2//!
3//! HTTP/JSON server exposing the same operations as the CLI. The server
4//! is stateful (it owns a `Store` instance) so agents don't pay sandbox
5//! startup cost per request.
6//!
7//! Endpoints:
8//! POST /v1/parse { source } → CanonicalAst | [SyntaxError]
9//! POST /v1/check { source } → { ok: true } | [TypeError]
10//! POST /v1/publish { source, activate? } → [{ stage_id, sig_id, status }]
11//! GET /v1/stage/<id> → { metadata, ast, status }
12//! GET /v1/stage/<id>/attestations → { attestations: [Attestation] }
13//! POST /v1/run { source, fn, args, policy } → { run_id, output | error }
14//! GET /v1/trace/<run_id> → TraceTree
15//! POST /v1/replay { source, fn, args, policy, overrides } → { run_id, output | error }
16//! GET /v1/diff?a=&b= → Divergence | { divergence: null }
17//! POST /v1/merge/start { src_branch, dst_branch } → { merge_id, conflicts, ... }
18//! POST /v1/merge/<id>/resolve { resolutions: [...] } → { verdicts, remaining_conflicts }
19//! POST /v1/merge/<id>/commit → { new_head_op, dst_branch } | 422 conflicts remaining
20//! GET /v1/health → { ok: true }
21//!
22//! Web (lex-tea v2, read-only HTML; human-only audit + triage):
23//! GET / → activity stream (recent attestations)
24//! GET /web/attention → exceptions queue (Failed / Inconclusive,
25//! stale merge sessions)
26//! GET /web/trust → per-producer rollup (pass rate, latest
27//! failure)
28//! GET /web/branches → branch list
29//! GET /web/branch/<name> → fns on a branch (detail)
30//! GET /web/stage/<id> → stage info + attestation trail (detail)
31
32pub mod handlers;
33mod web;
34pub mod mcp;
35
36use std::path::PathBuf;
37use std::sync::Arc;
38
39pub fn serve(port: u16, store_root: PathBuf) -> anyhow::Result<()> {
40 let server = tiny_http::Server::http(("127.0.0.1", port))
41 .map_err(|e| anyhow::anyhow!("bind failed: {e}"))?;
42 let state = Arc::new(handlers::State::open(store_root)?);
43 serve_on(server, state);
44 Ok(())
45}
46
47/// MCP server (#171). Same `State` shape as the HTTP server,
48/// stdio transport instead of TCP. Designed for hosts that
49/// spawn `lex serve --mcp` as a subprocess and pipe JSON-RPC
50/// over stdin/stdout.
51pub fn serve_mcp_stdio(store_root: PathBuf) -> anyhow::Result<()> {
52 let state = Arc::new(handlers::State::open(store_root)?);
53 mcp::serve_mcp(state)?;
54 Ok(())
55}
56
57/// Test/embedded entry: takes an already-bound `Server` and runs until it
58/// stops accepting requests. Returns immediately when the `Server` is
59/// dropped on another thread.
60pub fn serve_on(server: tiny_http::Server, state: Arc<handlers::State>) {
61 for request in server.incoming_requests() {
62 let state = Arc::clone(&state);
63 let _ = handlers::handle(state, request);
64 }
65}