Skip to main content

khive_runtime/
events_split.rs

1//! Events-daemon split (ADR-170): the audit lane leaves the domain store.
2//!
3//! The ADR-133 idempotent audit batch is the measured bulk of event write
4//! volume, and in a single-store deployment its rows queue on the same SQLite
5//! writer lane as domain mutations. This module moves that lane into a
6//! dedicated events daemon that owns the events database, reachable over a
7//! Unix socket with the same length-prefixed framing and peer-uid admission the
8//! main daemon socket uses. Plain event appends stay on the domain store —
9//! the legacy `events` table has raw-SQL consumers (schedule provenance, kg
10//! projection guards, graph-query substrate unions) whose correctness
11//! depends on finding those rows there.
12//!
13//! Cooperating pieces:
14//!
15//! - [`run_events_daemon`] — the server loop the `events-daemon` subcommand
16//!   runs: binds the events socket, owns the only resident writer of the
17//!   events database, and serves append/read requests through the ordinary
18//!   `SqlEventStore`.
19//! - [`EventsSplitClient`] — one per domain process. Plain appends ride a
20//!   bounded in-memory queue drained by a background forwarder
21//!   (fire-and-forget; overflow or a dead daemon drops the batch, counts it,
22//!   and logs — the loss-tolerant durability class made concrete; unused by
23//!   the default routing until telemetry producers opt in). Idempotent
24//!   audit-batch appends and reads are synchronous framed round-trips with
25//!   bounded timeouts, because their callers are background flushers or query
26//!   paths, never the dispatch hot path.
27//! - [`ForwardingEventStore`] — the lane-side [`EventStore`] over the socket.
28//!   Preflight validation delegates to an in-memory `SqlEventStore`, so the
29//!   ADR-133 audit-batch seam keeps its pre-enqueue shape check without any
30//!   I/O on the dispatch path.
31//! - [`SplitEventStore`] — the per-namespace handle
32//!   [`crate::runtime::KhiveRuntime::events`] returns when the split is
33//!   configured: routes the idempotent lane to the events store, plain
34//!   appends to the legacy store, and merges reads across both.
35//!
36//! Domain availability never depends on events-daemon liveness: every failure
37//! path here degrades (drop + count + log, or a typed storage error for the
38//! synchronous lanes) instead of blocking the caller.
39
40use std::collections::BTreeMap;
41use std::path::{Path, PathBuf};
42#[cfg(unix)]
43use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
44use std::sync::Arc;
45#[cfg(unix)]
46use std::time::Duration;
47
48use async_trait::async_trait;
49use khive_db::{StorageBackend, WalCeilingPolicy};
50use khive_storage::event::{
51    EventGroupBy, EventPageQuery, EventPageWindow, IdempotentEventBatchResult,
52};
53use khive_storage::{
54    BatchWriteSummary, Event, EventFilter, EventStore, Page, PageRequest, StorageError,
55    StorageResult,
56};
57use serde::{Deserialize, Serialize};
58
59#[path = "events_count_read_budget.rs"]
60mod events_count_read_budget;
61use events_count_read_budget::CountReadBudget;
62#[doc(hidden)]
63pub use events_count_read_budget::WireReadFailure;
64#[cfg(unix)]
65use events_count_read_budget::{count_storage_error_response, restore_count_read_failure};
66
67#[cfg(unix)]
68use tokio::net::{UnixListener, UnixStream};
69use uuid::Uuid;
70
71#[cfg(unix)]
72use crate::daemon::{read_frame, write_frame};
73
74mod daemon;
75mod forwarder;
76mod paths;
77mod protocol;
78mod registry;
79mod server;
80mod stores;
81
82pub use stores::*;
83
84pub use forwarder::*;
85
86use server::*;
87
88pub use daemon::*;
89
90pub use protocol::*;
91
92pub use registry::*;
93
94pub use paths::*;
95
96#[cfg(unix)]
97#[path = "events_split_socket_path.rs"]
98pub(crate) mod socket_path;
99#[cfg(unix)]
100pub use socket_path::validate_events_socket_path;
101
102// ---------------------------------------------------------------------------
103// Wire protocol
104// ---------------------------------------------------------------------------
105
106// ---------------------------------------------------------------------------
107// Server side
108// ---------------------------------------------------------------------------
109
110#[cfg(unix)]
111#[path = "events_split_policy_entries.rs"]
112mod policy_entries;
113#[cfg(unix)]
114use policy_entries::standalone_daemon_policies;
115#[cfg(unix)]
116pub use policy_entries::{
117    run_events_daemon_with_wal_ceiling, supervise_events_daemon_with_wal_ceiling,
118};
119
120#[cfg(all(test, unix))]
121#[path = "events_wal_policy_tests.rs"]
122mod wal_policy_tests;
123
124// ---------------------------------------------------------------------------
125// Client side
126// ---------------------------------------------------------------------------
127
128// ---------------------------------------------------------------------------
129// The per-namespace EventStore handle
130// ---------------------------------------------------------------------------
131
132#[cfg(all(test, unix))]
133#[path = "events_count_read_budget_tests.rs"]
134mod count_read_budget_tests;
135
136#[cfg(test)]
137#[path = "events_split_sqlite_full_tests.rs"]
138mod sqlite_full_tests;
139
140#[cfg(test)]
141mod tests;