sqry_daemon/lib.rs
1//! `sqry-daemon` — long-lived code-graph service.
2//!
3//! The daemon (`sqryd` binary) owns one or more loaded code graphs in memory,
4//! watches source trees for changes, and serves CLI / LSP / MCP clients over a
5//! shared Unix-domain socket (named pipe on Windows). The goal is to amortise
6//! graph-load cost across every sqry invocation on a machine while preserving
7//! the semantic guarantees of direct-mode sqry (bijective per-file buckets,
8//! tombstone compaction, `ArcSwap` publish, etc.).
9//!
10//! # Architecture at a glance
11//!
12//! - [`config`] — parses `~/.config/sqry/daemon.toml` into a [`config::DaemonConfig`]
13//! with every tuning knob from the Amendment-2 design (memory limits, working-set
14//! multipliers, stale-serve age cap, debounce timing, interner compaction
15//! threshold, log rotation, socket path).
16//! - [`workspace`] *(Task 6)* — `WorkspaceManager` owns `LoadedWorkspace` state
17//! (§G admission accounting, §H rebuild plumbing, §F bijection check).
18//! - [`rebuild`] *(Task 7)* — per-workspace rebuild lane + coalescing (§J).
19//! - [`ipc`] *(Task 8)* — JSON-RPC over UDS with a standard response envelope.
20//! - [`lifecycle`] *(Task 9)* — pidfile locking, signal handling, service
21//! unit generators.
22//! - [`client`] *(Task 10)* — client library used by `sqry-cli` /
23//! `sqry-lsp --daemon` / `sqry-mcp --daemon` to connect to a running daemon
24//! and auto-start one if necessary.
25//!
26//! Only [`config`] and the public error type [`DaemonError`] are in the surface
27//! today; later tasks in this plan land the other modules in order.
28
29pub mod config;
30/// Task 9 U10 — production sqryd binary entry point.
31///
32/// Owns the clap CLI (`SqrydCli`), the ordered startup / shutdown lifecycle
33/// (`run()`), and every `run_start` / `run_stop` / `run_status` /
34/// `run_install_*` / `run_print_config` dispatcher. `main.rs` calls
35/// `sqry_daemon::entrypoint::main_impl()` which parses the CLI, builds the
36/// tokio runtime, and maps every error to a POSIX `sysexits.h` exit code via
37/// `DaemonError::exit_code()`.
38pub mod entrypoint;
39pub mod error;
40pub mod ipc;
41/// Task 9 — daemon binary lifecycle: pidfile locking, signal handling, service
42/// unit generators, log rotation, and auto-spawn primitives.
43///
44/// The module is built up incrementally across Task 9 units. Only the units
45/// that have landed so far are present; later units (U3–U10) add submodules as
46/// they are implemented.
47pub mod lifecycle;
48/// Phase 8c U8 — in-daemon MCP host.
49///
50/// Hosts an rmcp `ServerHandler` in-process for each MCP shim
51/// byte-pump connection (see [`mcp_host::host_mcp_on_streams`]),
52/// routing every `tools/call` through Phase 8b's
53/// `daemon_adapter::execute_*_for_daemon` path via the shared
54/// [`ipc::tool_core::classify_and_execute`] pipeline. MCP tool
55/// behaviour is bit-identical to direct sqryd JSON-RPC tool dispatch.
56pub mod mcp_host;
57pub mod rebuild;
58pub mod workspace;
59
60pub use config::{
61 DEFAULT_IPC_SHUTDOWN_DRAIN_SECS, DaemonConfig, ESTIMATE_FINAL_PER_FILE_BYTES,
62 ESTIMATE_STAGING_PER_FILE_BYTES, INTERNER_BUILDER_OVERHEAD_RATIO, SocketConfig,
63 WORKING_SET_MULTIPLIER, WorkspaceConfig,
64};
65pub use error::{DaemonError, DaemonResult};
66pub use ipc::{
67 CancelRebuildResult, DaemonHello, DaemonHelloResponse, IpcServer, JsonRpcError, JsonRpcId,
68 JsonRpcPayload, JsonRpcRequest, JsonRpcResponse, JsonRpcVersion, RebuildResult,
69 ResponseEnvelope, ResponseMeta,
70};
71pub use rebuild::{
72 CapturedIteration, RebuildDispatcher, RebuildMode, TestCapture, TestGate, decide_mode,
73};
74pub use workspace::{
75 BACKOFF_SCHEDULE, DaemonStatus, EmptyGraphBuilder, FailingGraphBuilder, LoadedWorkspace,
76 MemoryStatus, NoOpHook, OldGraphToken, PendingRebuild, RealWorkspaceBuilder,
77 RebuildReservation, ServeVerdict, SharedHook, SqrydHook, StalenessVerdict, WorkingSetInputs,
78 WorkspaceBuilder, WorkspaceKey, WorkspaceManager, WorkspaceState, WorkspaceStatus,
79 backoff_delay_for, classify_staleness, noop_hook, spawn_hook, working_set_estimate,
80};
81
82/// JSON-RPC error code: per-tool invocation exceeded
83/// `DaemonConfig::tool_timeout_secs`. Emitted by
84/// `tool_core::classify_and_execute` (Task 8 Phase 8c U6) when the
85/// `tokio::time::timeout(tool_timeout, spawn_blocking(run))` outer
86/// timer fires. The detached `JoinHandle` is dropped — the OS thread
87/// may continue executing the tool closure but its result is
88/// discarded.
89///
90/// Source: Task 8 Phase 8c design §O (iter-2 Codex-approved wire contract).
91pub const JSONRPC_TOOL_TIMEOUT: i32 = -32000;
92
93/// JSON-RPC error code: workspace build failed and no prior good graph exists.
94///
95/// Source: Amendment 1 §C, Amendment 2 §G.7.
96pub const JSONRPC_WORKSPACE_BUILD_FAILED: i32 = -32001;
97
98/// JSON-RPC error code: the workspace is serving a Failed state, but the last
99/// successful build is older than `stale_serve_max_age_hours`.
100///
101/// Source: Amendment 1 §C.
102pub const JSONRPC_WORKSPACE_STALE_EXPIRED: i32 = -32002;
103
104/// JSON-RPC error code: admission control could not satisfy a reservation
105/// after evicting every non-pinned workspace.
106///
107/// Source: Amendment 2 §G.1, §G.7.
108pub const JSONRPC_MEMORY_BUDGET_EXCEEDED: i32 = -32003;
109
110/// JSON-RPC error code: the workspace was evicted or removed between a
111/// rebuild dispatch and its admission / publish commit. Callers must treat
112/// this as a terminal signal on the affected `WorkspaceKey` — subsequent
113/// dispatches require a fresh `get_or_load` first.
114///
115/// Source: Amendment 2 §J (same-workspace rebuild serialization), Task 7
116/// Phase 7b1 (runner-role gate + `reserve_rebuild` eviction check).
117///
118/// # Daemon public JSON-RPC error codes (authoritative table)
119///
120/// | Code | Variant | Semantics |
121/// |---------|------------------------|----------------------------------------------------------------|
122/// | -32000 | `ToolTimeout` | Per-tool `tool_timeout_secs` deadline elapsed (Phase 8c U6). |
123/// | -32001 | `WorkspaceBuildFailed` | Build failed, no prior good graph. |
124/// | -32002 | `WorkspaceStaleExpired`| Stale-serve window exceeded `stale_serve_max_age_hours`. |
125/// | -32003 | `MemoryBudgetExceeded` | Admission cannot fit even after evicting all non-pinned. |
126/// | -32004 | `WorkspaceEvicted` | Workspace gone mid-rebuild; caller must re-`get_or_load`. |
127/// | -32602 | `InvalidArgument` | Tool-argument validation failure (JSON-RPC standard). |
128/// | -32603 | `Internal` | Catch-all bubbled from `sqry_mcp::daemon_adapter` execution. |
129/// | n/a | `AlreadyRunning` | Another sqryd holds the pidfile lock (Task 9 U1). Exit 75. |
130/// | n/a | `AutoStartTimeout` | `start_detached` socket poll timed out (Task 9 U1). Exit 69. |
131/// | n/a | `SignalSetup` | `tokio::signal` handler install failed (Task 9 U1). Exit 70. |
132pub const JSONRPC_WORKSPACE_EVICTED: i32 = -32004;
133
134/// JSON-RPC 2.0 standard "Invalid params" error code.
135///
136/// Surfaced by `tool_core` argument validation (Phase 8c U6) BEFORE
137/// workspace classification runs — e.g. `resolve_index_root` failures
138/// and missing `path` arguments in MCP tool args.
139pub const JSONRPC_INVALID_PARAMS: i32 = -32602;
140
141/// JSON-RPC 2.0 standard "Internal error" code. Catch-all for errors
142/// bubbling from `sqry_mcp::daemon_adapter` tool execution that don't
143/// map to a more specific `DaemonError` variant.
144pub const JSONRPC_INTERNAL_ERROR: i32 = -32603;
145
146/// Version of the daemon wire envelope (`DaemonHelloResponse.envelope_version`).
147///
148/// Re-exported from `sqry-daemon-protocol` so callers that only depend on
149/// `sqry-daemon` (or on `sqry-daemon-client`) both see the same single source
150/// of truth. See [`sqry_daemon_protocol::ENVELOPE_VERSION`] for the canonical
151/// definition and bump policy.
152pub use sqry_daemon_protocol::ENVELOPE_VERSION;
153
154// ---------------------------------------------------------------------------
155// Shared test-only ENV_LOCK
156// ---------------------------------------------------------------------------
157
158/// Single process-wide mutex for tests that manipulate `XDG_RUNTIME_DIR`.
159///
160/// Multiple test modules (`pidfile`, `detach`, `config`) run as threads in the
161/// same binary. Each module previously had its own `ENV_LOCK`, which allowed
162/// concurrent `XDG_RUNTIME_DIR` mutations and produced flaky pidfile-PID
163/// mismatches. This shared lock serialises all env-var mutations across every
164/// `#[cfg(test)]` module in the crate.
165#[cfg(test)]
166pub(crate) static TEST_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());