talon_http/lib.rs
1//! # talon-http — CoPilot HTTP API channel
2//!
3//! See `docs/RUST_ARCHITECTURE.md` §3.4 (untrusted data) and §3.5 (audit).
4//!
5//! An axum server that CoPilot talks to (plus the MemPalace bridge), exposed
6//! to the host as a [`talon_core::channel::Channel`] owning every
7//! `http:copilot*` JID.
8//!
9//! ## Lanes
10//! - `http:copilot` — the shared chat lane (callers that name no user).
11//! - `http:copilot:investigate` — investigations; ephemeral session, its
12//! output reaches CoPilot through the MCP write-back tools, not SSE.
13//! - `http:copilot:u:<id>` — one in-memory lane per CoPilot user, cloned
14//! from the base group (same folder/mounts, own session + IPC namespace
15//! `copilot-u-<lowercased id, ≤ 20 chars>-<32 hex of sha256(exact id)>`,
16//! e.g. `copilot-u-bob-81b637d8fcd2c6da6359e6963113a117`), dropped again
17//! by the idle-lane sweep. Ids must match `^[A-Za-z0-9_-]{1,48}$`; anything
18//! else is rejected with 400 rather than stripped into another user's lane.
19//! The 128-bit hash makes it infeasible to choose an id whose key collides
20//! with another user's (N-8).
21//!
22//! ## Endpoints
23//! | Method | Path | Notes |
24//! |---|---|---|
25//! | GET | `/health` | unauthenticated liveness |
26//! | GET | `/status` | uptime, lanes, sessions, scheduled tasks, queue |
27//! | GET | `/metrics` | Prometheus text exposition |
28//! | POST | `/session/reset` | `{scope: chat\|investigate\|all, user_id?}` |
29//! | POST | `/investigate` | `{alert_id, customer_code, triggered_by?, template_override?}` |
30//! | GET | `/investigations` | `?limit=` recent investigation jobs |
31//! | GET | `/investigations/{id}` | one job |
32//! | GET | `/evals/{alert_id}` | eval JSON written by the investigation lane (`<data_dir>/sessions/copilot-investigate/evals/`) |
33//! | GET | `/templates` | prompt templates available for overrides |
34//! | POST | `/palace/lesson` | MemPalace `add_drawer` (503 unless a palace runner is configured) |
35//! | GET | `/palace/search` | MemPalace `search` (503 unless configured) |
36//! | POST | `/palace/forget` | MemPalace `delete_drawer` (503 unless configured) |
37//! | POST | `/message` | chat; answers stream back as SSE |
38//!
39//! ## Security
40//! Fail-closed API key (an unset `HTTP_API_KEY` rejects every request but
41//! `/health`), constant-time comparison via [`talon_security::auth`],
42//! per-IP auth-failure throttling, request body limit, strict input
43//! validation, security headers, allowlist-based CORS, audit events and
44//! investigation de-duplication. Served with [`talon_net::serve`]
45//! (header-read timeout, connection caps), bound to `127.0.0.1` unless
46//! configured otherwise. MemPalace code never runs on the host by default
47//! (see [`palace`]); agent-writable `evals/` and `prompts/` are read without
48//! following symlinks. Eval records come from the investigation lane's
49//! private state directory, so no other CoPilot lane can forge them (N-2);
50//! the shared `groups/<folder>/evals/` is read only with
51//! `COPILOT_EVALS_LEGACY_GROUP_DIR=true` (migration).
52//!
53//! ## Testing
54//! [`router`] builds the full axum [`axum::Router`] from an [`HttpState`];
55//! drive it with `tower::ServiceExt::oneshot`. The database and key
56//! verification are reached through the [`HttpStore`] and [`KeyVerifier`]
57//! seams so the server can be exercised without the real backends.
58
59#![warn(missing_docs)]
60
61pub mod auth;
62pub mod channel;
63pub mod config;
64mod files;
65pub mod investigate;
66pub mod lanes;
67mod metrics;
68pub mod palace;
69pub mod server;
70mod sse;
71pub mod store;
72pub mod validate;
73
74#[cfg(test)]
75mod tests;
76#[cfg(test)]
77mod testutil;
78
79pub use auth::{KeyVerifier, SecurityKeyVerifier};
80pub use channel::HttpChannel;
81pub use config::{ApiKey, HttpConfig};
82pub use lanes::{InvalidUserId, LaneIds, UserId, is_valid_user_id, lane_jid_for_user};
83pub use palace::{
84 CommandPalaceRunner, DisabledPalaceRunner, PalaceError, PalaceRunner, ScriptPalaceRunner,
85};
86pub use server::{HttpDeps, HttpState, router};
87pub use store::HttpStore;