//! The deadlines of three days (2026-10-31, lot F): an objection whose author //! answered and whose objector said nothing is lifted; a typo fix nobody //! answered applies. Run by the adoption sweeper (every 5 minutes) and once at //! start, so a deadline passed while the server was stopped is dealt with as //! soon as it starts again. Tests move the clock (`AppState::clock`), never sleep. use crate::AppState; use crate::adoption; use crate::error::{AppError, AppResult}; use crate::{objections, typos}; use axum::Json; use axum::extract::State; use serde::Deserialize; use serde_json::{Value, json}; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Swept { /// Objections lifted at their deadline. pub lifted: usize, /// Typo fixes applied at their deadline. pub applied: usize, /// Typo fixes that could not apply: their text was gone. pub stale: usize, } /// One pass: the objections, then the typo fixes, then the adoption of the /// documents where an objection was lifted (a version may now enter into force). pub async fn sweep(state: &AppState) -> AppResult { let (lifted, docs) = objections::due(state).await?; let fixes = typos::due(state).await?; for doc in &docs { adoption::run(state, doc).await?; } Ok(Swept { lifted, applied: fixes.applied, stale: fixes.stale }) } #[derive(Deserialize)] pub struct ClockBody { seconds: i64, } /// `POST /api/dev/clock` — the end-to-end tests move the server's clock forward /// and pass the deadlines at once. Open with the dev login only, like it. pub async fn dev_clock(State(state): State, Json(b): Json) -> AppResult> { if !state.cfg.dev_login { return Err(AppError::NotFound); } if b.seconds <= 0 || b.seconds > 400 * 86_400 { return Err(AppError::Invalid("seconds")); } state.clock.advance(b.seconds); let swept = sweep(&state).await?; Ok(Json(json!({ "now": state.clock.now(), "offset": state.clock.offset(), "lifted": swept.lifted, "applied": swept.applied, "stale": swept.stale, }))) }