Refactor recording metadata to JSON sidecar
Replaces the `.transcript.txt` sidecar with a structured `.json` file for recording metadata. This change consolidates transcript text, duration, and other potential metadata into a single, extensible JSON object. This also refactors the `TranscriptState` enum to better represent the on-disk state (absence of file means pending) and the in-memory representation. The `Transcript` enum now specifically models the terminal outcomes of the transcriber (`Silent` or `Content`). The commit includes updates to documentation, data structures, path handling, and various tests to align with the new metadata format.
This commit is contained in:
@@ -136,10 +136,8 @@ async fn transient_whisper_failure_is_reenqueued_and_recovers() {
|
||||
".m4a.failed must not exist after transient error"
|
||||
);
|
||||
assert!(
|
||||
!case_dir
|
||||
.join("2026-04-15T20-30-00Z.transcript.txt")
|
||||
.exists(),
|
||||
"no transcript yet — the 503 produced nothing"
|
||||
!case_dir.join("2026-04-15T20-30-00Z.json").exists(),
|
||||
"no metadata sidecar yet — the 503 produced nothing"
|
||||
);
|
||||
|
||||
// Act 2: heal re-enqueues the pending .m4a (second mock → 200).
|
||||
@@ -154,22 +152,26 @@ async fn transient_whisper_failure_is_reenqueued_and_recovers() {
|
||||
)
|
||||
.await;
|
||||
|
||||
// Assert: the transcript lands. Poll because the worker runs
|
||||
// asynchronously after the heal hands off the job.
|
||||
let transcript = case_dir.join("2026-04-15T20-30-00Z.transcript.txt");
|
||||
// Assert: the recording metadata sidecar lands. Poll because the
|
||||
// worker runs asynchronously after the heal hands off the job.
|
||||
let meta_path = case_dir.join("2026-04-15T20-30-00Z.json");
|
||||
let start = std::time::Instant::now();
|
||||
while !transcript.exists() {
|
||||
while !meta_path.exists() {
|
||||
if start.elapsed() > Duration::from_secs(10) {
|
||||
panic!(
|
||||
"transcript did not appear within 10s (whisper hits: {})",
|
||||
"meta sidecar did not appear within 10s (whisper hits: {})",
|
||||
whisper.received_requests().await.unwrap().len()
|
||||
);
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(25)).await;
|
||||
}
|
||||
|
||||
let body = std::fs::read_to_string(&transcript).unwrap();
|
||||
assert_eq!(body, "recovered text");
|
||||
let bytes = std::fs::read(&meta_path).unwrap();
|
||||
let meta: doctate_common::RecordingMeta = serde_json::from_slice(&bytes).unwrap();
|
||||
match meta.transcript {
|
||||
doctate_common::Transcript::Content { text } => assert_eq!(text, "recovered text"),
|
||||
other => panic!("expected Transcript::Content, got {other:?}"),
|
||||
}
|
||||
|
||||
// Teardown: close the worker channel so the background task exits.
|
||||
drop(tx_t);
|
||||
|
||||
Reference in New Issue
Block a user