Refactor document handling to JSON envelope

The `document.md` file has been replaced by `document.json`. This new
format acts as a JSON envelope, containing the original markdown content
along with metadata such as `covered_mtime`, `written_at`, and
`backend_id`.

This change addresses a bug where new recordings arriving during an LLM
analysis call could be ignored. The `covered_mtime` field in the new
JSON envelope ensures that the document's metadata accurately reflects
the state of recordings at the time of analysis, preventing incorrect
"analysis current" states.

Additionally, the `analysis_input.json` file is now removed upon
successful analysis, and a failure marker is used to indicate analysis
failures. This ensures that a case is re-analyzed if necessary.

The `analysis_idle_threshold_secs` configuration option has been
introduced to control the delay before a case in the `Required` state is
automatically promoted to `Idle` and enqueued for LLM analysis.
This commit is contained in:
2026-05-05 14:01:43 +02:00
parent c09a74cef3
commit 9e8db7518d
12 changed files with 180 additions and 38 deletions
+19 -2
View File
@@ -2,12 +2,29 @@
//! canonical source-of-truth locations.
//!
//! Tests that need to read, write, or assert the absence of per-case
//! artefacts (`oneliner.json`, `.closed`, `document.md`,
//! artefacts (`oneliner.json`, `.closed`, `document.json`,
//! `analysis_input.json`) pull these names from here instead of
//! inlining string literals. If the server renames a sidecar in the
//! future, test call-sites fail to compile instead of silently
//! asserting against the old name.
use std::path::Path;
pub use doctate_common::ONELINER_FILENAME;
pub use doctate_server::analyze::{ANALYSIS_INPUT_FILE, DOCUMENT_FILE};
pub use doctate_server::analyze::{ANALYSIS_INPUT_FILE, DOCUMENT_FILE, Document};
pub use doctate_server::paths::CLOSE_MARKER;
/// Seed a `document.json` for tests. `covered_mtime` is the RFC3339
/// snapshot mtime the document is supposed to "cover" — for tests that
/// don't care about the auto-trigger state machine, pass any RFC3339
/// string (e.g. `"1970-01-01T00:00:00Z"`).
pub fn write_document_for_test(case_dir: &Path, content_md: &str, covered_mtime: &str) {
let doc = Document {
covered_mtime: covered_mtime.to_owned(),
written_at: doctate_common::timestamp::now_rfc3339(),
backend_id: "test".to_owned(),
content_md: content_md.to_owned(),
};
let bytes = serde_json::to_vec_pretty(&doc).expect("serialize test Document");
std::fs::write(case_dir.join(DOCUMENT_FILE), bytes).expect("write test document.json");
}
+17 -2
View File
@@ -66,6 +66,7 @@ pub struct TestConfig {
whisper_url: Option<String>,
whisper_timeout_seconds: Option<u64>,
ollama_url: Option<String>,
idle_threshold_secs: Option<u64>,
label: &'static str,
}
@@ -85,6 +86,7 @@ impl TestConfig {
whisper_url: None,
whisper_timeout_seconds: None,
ollama_url: None,
idle_threshold_secs: None,
label: "common",
}
}
@@ -152,6 +154,15 @@ impl TestConfig {
self
}
/// Idle threshold in seconds: how long after the latest recording mtime
/// a `Required` case is auto-promoted to `Idle` (and enqueued). Default
/// is `Config::test_default()` (= `0`, eager). Tests that exercise the
/// "wait for quiet period" branch set a positive value.
pub fn with_idle_threshold(mut self, secs: u64) -> Self {
self.idle_threshold_secs = Some(secs);
self
}
/// Materialize the config alone. Bucket-B overrides (`with_llm`,
/// `with_whisper`, `with_ollama`) are silently dropped — call
/// [`build_pair`] when those need to take effect.
@@ -170,12 +181,16 @@ impl TestConfig {
.map(|u| (u.api_key.clone(), u.slug.clone()))
.collect();
let config = Arc::new(Config {
let mut config = Config {
data_path,
users: self.users,
api_keys,
..Config::test_default()
});
};
if let Some(v) = self.idle_threshold_secs {
config.analysis_idle_threshold_secs = v;
}
let config = Arc::new(config);
let mut settings = Settings::default();
// LLM-related fields used to live on `Settings`; with the multi-backend
+4 -1
View File
@@ -25,7 +25,10 @@ pub mod session;
pub mod users;
// Re-exports for ergonomic `use common::*;` in most test files.
pub use artefacts::{ANALYSIS_INPUT_FILE, CLOSE_MARKER, DOCUMENT_FILE, ONELINER_FILENAME};
pub use artefacts::{
ANALYSIS_INPUT_FILE, CLOSE_MARKER, DOCUMENT_FILE, Document, ONELINER_FILENAME,
write_document_for_test,
};
pub use config::{TestConfig, unique_tmpdir};
pub use doctate_common::Transcript;
pub use http::{