feat: Add per-case dir oneliner locks
Introduces `OnelinerLocks` to serialize read-modify-write operations on
`oneliner.json`. This prevents race conditions between the manual
override API (`PUT /api/cases/{case_id}/oneliner`) and the transcription
worker's auto-regeneration process.
The manual override now acquires a lock specific to the `case_dir`
before writing. The transcription worker's `update_oneliner` function
also acquires the same lock around its post-LLM re-read-and-write
sequence. This ensures that a doctor's manual edit always takes
precedence, even if it arrives while an auto-regeneration is in
progress.
This change also includes:
- A new `routes::oneliner_override` module for the PUT handler.
- Validation for the oneliner text length and emptiness.
- Integration tests for the override functionality, covering happy path,
error conditions, early latching, race conditions, and parallel PUTs.
This commit is contained in:
@@ -8,6 +8,7 @@ pub mod events;
|
||||
pub mod gazetteer;
|
||||
pub mod magic_link;
|
||||
pub mod models;
|
||||
pub mod oneliner_locks;
|
||||
pub mod paths;
|
||||
pub mod retention;
|
||||
pub mod routes;
|
||||
@@ -26,6 +27,7 @@ use analyze::AnalyzeSender;
|
||||
use config::Config;
|
||||
use gazetteer::Gazetteer;
|
||||
use magic_link::MagicLinkStore;
|
||||
use oneliner_locks::OnelinerLocks;
|
||||
use transcribe::TranscribeSender;
|
||||
use web_session::SessionStore;
|
||||
|
||||
@@ -78,6 +80,7 @@ pub struct PipelineState {
|
||||
pub transcribe_busy: TranscribeBusy,
|
||||
pub transcribe_tx: TranscribeSender,
|
||||
pub oneliner_heal_busy: OnelinerHealBusy,
|
||||
pub oneliner_locks: OnelinerLocks,
|
||||
}
|
||||
|
||||
/// Shared application state. Clonable — all fields are cheap to clone
|
||||
@@ -92,6 +95,7 @@ pub struct AppState {
|
||||
pub analyze_busy: AnalyzeBusy,
|
||||
pub transcribe_busy: TranscribeBusy,
|
||||
pub oneliner_heal_busy: OnelinerHealBusy,
|
||||
pub oneliner_locks: OnelinerLocks,
|
||||
pub events_tx: events::EventSender,
|
||||
pub http_client: reqwest::Client,
|
||||
pub vocab: Arc<Gazetteer>,
|
||||
@@ -145,6 +149,12 @@ impl FromRef<AppState> for OnelinerHealBusy {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for OnelinerLocks {
|
||||
fn from_ref(state: &AppState) -> Self {
|
||||
state.oneliner_locks.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for PipelineState {
|
||||
fn from_ref(state: &AppState) -> Self {
|
||||
PipelineState {
|
||||
@@ -153,6 +163,7 @@ impl FromRef<AppState> for PipelineState {
|
||||
transcribe_busy: state.transcribe_busy.clone(),
|
||||
transcribe_tx: state.transcribe_tx.clone(),
|
||||
oneliner_heal_busy: state.oneliner_heal_busy.clone(),
|
||||
oneliner_locks: state.oneliner_locks.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,6 +211,7 @@ pub fn create_router_and_session_store(config: Arc<Config>) -> (Router, SessionS
|
||||
analyze_busy: AnalyzeBusy(Arc::new(AtomicBool::new(false))),
|
||||
transcribe_busy: TranscribeBusy(Arc::new(AtomicBool::new(false))),
|
||||
oneliner_heal_busy: OnelinerHealBusy(Arc::new(AtomicBool::new(false))),
|
||||
oneliner_locks: OnelinerLocks::new(),
|
||||
events_tx: events::channel(),
|
||||
http_client: reqwest::Client::new(),
|
||||
vocab: Arc::new(Gazetteer::empty()),
|
||||
|
||||
Reference in New Issue
Block a user