Introduce boot_time to evaluate_state
The `boot_time` parameter is introduced to `evaluate_state` and its callers. This parameter acts as a lower bound for the "seen at" timestamp used in determining the `Idle` state for cases. Previously, the `elapsed` duration was calculated as `now.duration_since(latest_mtime)`. This meant that a server restart could immediately cause old recordings to be classified as `Idle` if their modification times were significantly in the past, potentially triggering a large number of LLM calls upon the first render of the `/cases` page. By using `seen_at = std::cmp::max(latest_mtime, boot_time)`, the idle calculation now ensures that a fresh server start respects a grace period. Old recordings will not satisfy the idle threshold until the elapsed time since the server's boot (or the recording's modification time, whichever is later) exceeds the `idle_threshold`. `SystemTime::UNIX_EPOCH` is used as the default `boot_time` in tests and in `create_router_and_session_store_with_settings`. This preserves existing test behavior where the idle grace period is effectively disabled. For production, `main.rs` now captures the server's actual boot time and passes it down. This change also refactors `evaluate_case` and `try_enqueue` to accept the new `boot_time` parameter. `try_enqueue_all_for_user` is updated to pass `boot_time` along. `boot_scan_state` and `compute_state_for_user` in `recovery.rs` also receive `boot_time`. Additionally, a new test case `analyze_required_endpoint_enqueues_only_required_cases` is added to verify the behavior of the `/cases/analyze-required` endpoint. This endpoint now correctly enqueues only `Required`
This commit is contained in:
@@ -87,6 +87,15 @@ pub struct PipelineState {
|
||||
pub oneliner_locks: OnelinerLocks,
|
||||
}
|
||||
|
||||
/// Newtype for the server's start timestamp. Functions as the lower
|
||||
/// bound for the analyze idle-trigger: a case is only auto-enqueued
|
||||
/// once `now - max(latest_recording_mtime, boot_time) >= threshold`.
|
||||
/// Without this, a server restart would treat every case with an
|
||||
/// older recording as immediately `Idle` and spawn a thundering herd
|
||||
/// of LLM calls on the first `/cases` render.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BootTime(pub std::time::SystemTime);
|
||||
|
||||
/// Shared application state. Clonable — all fields are cheap to clone
|
||||
/// (`Arc` and `mpsc::Sender` / `broadcast::Sender`).
|
||||
#[derive(Clone)]
|
||||
@@ -104,6 +113,7 @@ pub struct AppState {
|
||||
pub events_tx: events::EventSender,
|
||||
pub http_client: reqwest::Client,
|
||||
pub vocab: Arc<Gazetteer>,
|
||||
pub boot_time: BootTime,
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for Arc<Config> {
|
||||
@@ -197,6 +207,12 @@ impl FromRef<AppState> for Arc<Gazetteer> {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for BootTime {
|
||||
fn from_ref(state: &AppState) -> Self {
|
||||
state.boot_time
|
||||
}
|
||||
}
|
||||
|
||||
/// Test/simple entrypoint: jobs pushed into either channel are dropped
|
||||
/// because the receivers are not retained. Use [`create_router_with_state`]
|
||||
/// from `main.rs` where real workers own the receivers.
|
||||
@@ -242,6 +258,11 @@ pub fn create_router_and_session_store_with_settings(
|
||||
events_tx: events::channel(),
|
||||
http_client: reqwest::Client::new(),
|
||||
vocab: Arc::new(Gazetteer::empty()),
|
||||
// UNIX_EPOCH = "the server has always been up" — disables the
|
||||
// post-boot grace window in tests, which is what most tests want
|
||||
// anyway. Tests that exercise the grace-window itself construct
|
||||
// an `AppState` with an explicit recent `BootTime`.
|
||||
boot_time: BootTime(std::time::SystemTime::UNIX_EPOCH),
|
||||
});
|
||||
(router, session_store)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user