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//! POST /v1/patch { stage_id, patch, activate?, branch?, intent? } → { new_stage_id, op_id, ... }
12//! POST /v1/transform { branch, intent?, transform: {kind, ...} } → { op_id, new_head, ... } (see `transform_http`)
13//! GET /v1/stage/<id> → { metadata, ast, status }
14//! GET /v1/stage/<id>/attestations → { attestations: [Attestation] }
15//! POST /v1/run { source, fn, args, policy } → { run_id, output | error }
16//! GET /v1/trace/<run_id> → TraceTree
17//! POST /v1/replay { source, fn, args, policy, overrides } → { run_id, output | error }
18//! GET /v1/diff?a=&b= → Divergence | { divergence: null }
19//! POST /v1/merge/start { src_branch, dst_branch } → { merge_id, conflicts, ... }
20//! POST /v1/merge/<id>/resolve { resolutions: [...] } → { verdicts, remaining_conflicts }
21//! POST /v1/merge/<id>/commit → { new_head_op, dst_branch } | 422 conflicts remaining
22//! POST /v1/ops/batch [OperationRecord] → { received, added, skipped, added_ids }
23//! POST /v1/attestations/batch [Attestation] → { received, added, skipped, added_ids }
24//! 403 ReservedProducer / ReservedKind for names and
25//! kinds an embedder reserved (`State::with_reserved_*`)
26//! GET /v1/branches/<name>/head → { branch, head_op } (probe for `lex op push`)
27//! POST /v1/blobs/missing { ids } → { missing } (#1007)
28//! POST /v1/blobs/batch [{ id, data_b64 }] → { received, added, skipped }
29//! POST /v1/blobs/fetch { ids } → { blobs: [{ id, data_b64 }] }
30//! GET /v1/health → { ok: true, caps: ["files-v1", "intent-origin-v1"] }
31//!
32//! Web (lex-tea v2, read-only HTML; human-only audit + triage):
33//! GET / → activity stream (recent attestations)
34//! GET /web/attention → exceptions queue (Failed / Inconclusive,
35//! stale merge sessions)
36//! GET /web/trust → per-producer rollup (pass rate, latest
37//! failure)
38//! GET /web/branches → branch list
39//! GET /web/branch/<name> → fns on a branch (detail)
40//! GET /web/stage/<id> → stage info + attestation trail (detail)
41
42pub mod handlers;
43mod branches_http;
44pub mod review_http;
45mod sync_http;
46mod ops_since_http;
47/// Derived issue/project state (#949 phase 3). `pub` so lex-hub can wrap the
48/// handlers with owner-session auth for the console, as it does for
49/// [`review_http`].
50pub mod issues_http;
51mod publish_examples;
52/// The typed-transform write surface (#837 piece A): `POST /v1/transform`,
53/// and the shared `apply_transform` that `lex ws transform` also calls.
54pub mod transform_http;
55mod web;
56pub mod mcp;
57
58/// Re-exported so embedders can build a [`handlers::State::policy_ceiling`]
59/// without taking a direct dependency on `lex-runtime`.
60pub use lex_runtime::Policy;
61
62use std::path::PathBuf;
63use std::sync::Arc;
64
65pub fn serve(port: u16, store_root: PathBuf) -> anyhow::Result<()> {
66 let server = tiny_http::Server::http(("127.0.0.1", port))
67 .map_err(|e| anyhow::anyhow!("bind failed: {e}"))?;
68 let state = Arc::new(handlers::State::open(store_root)?);
69 serve_on(server, state);
70 Ok(())
71}
72
73/// MCP server (#171). Same `State` shape as the HTTP server,
74/// stdio transport instead of TCP. Designed for hosts that
75/// spawn `lex serve --mcp` as a subprocess and pipe JSON-RPC
76/// over stdin/stdout.
77pub fn serve_mcp_stdio(store_root: PathBuf) -> anyhow::Result<()> {
78 let state = Arc::new(handlers::State::open(store_root)?);
79 mcp::serve_mcp(state)?;
80 Ok(())
81}
82
83/// Test/embedded entry: takes an already-bound `Server` and runs until it
84/// stops accepting requests. Returns immediately when the `Server` is
85/// dropped on another thread.
86pub fn serve_on(server: tiny_http::Server, state: Arc<handlers::State>) {
87 for request in server.incoming_requests() {
88 let state = Arc::clone(&state);
89 let _ = handlers::handle(state, request);
90 }
91}