Validate gated Google retrieval and record unchanged localization quality

This commit is contained in:
karl committed 2026-10-13 13:49:23 +02:00
1 parent af04cec27c
commit 59908adfa6
5 files changed
+267 -5

No files matched your search

+11
View File
@@ -19,6 +19,17 @@ ranks are retained; at most the first 100 segment hits per query are exported fo
the existing scorer. Ties use stable source order. No confidence threshold or
oracle matching is applied.
The additional frozen `gated-vad-v1` diagnostic policy accepts only the exact
VAD report SHA-256 `093be2005b57fe16a8f85161cc9da8966e70e2050cf19fa4122531e304dd70df`.
The ASR report embeds its original UTF-8 bytes and parsed document; both must
agree. Source probes, decoder hashes and the pinned base Whisper worker must
match. The ranker independently rebuilds every contiguous window from the frozen
bounded source-sample intersections, retaining interval identity and explicit
sub-millisecond tail remainder. Omitted intervals, changed windows or claimed
no-speech status without frozen empty intervals are rejected before inference.
All five sources remain explicit competitors; no relevance label selects which
source receives ASR. Existing ungated reports still require full audio coverage.
```powershell
cargo test --manifest-path tools/speech-rank/Cargo.toml --locked
cargo build --release --manifest-path tools/speech-rank/Cargo.toml --locked
+193
View File
@@ -0,0 +1,193 @@
//! Gated diagnostics are anchored to exact frozen VAD bytes, never claimed windows.
use super::*;
pub const POLICY: &str = "gated-vad-v1;source-intersections;integer-ms-tail-remainder-recorded;contiguous30s;independent-process;auto-language;segment-times";
const VAD_SHA: &str = "093be2005b57fe16a8f85161cc9da8966e70e2050cf19fa4122531e304dd70df";
pub fn verified_report(asr: &Value) -> Result<Option<Value>> {
if asr["policy"] != POLICY {
return Ok(None);
}
let bytes = asr["vad_report_utf8"]
.as_str()
.context("Missing original VAD bytes")?
.as_bytes();
ensure!(
bytes.len() <= 16 * 1024 * 1024
&& format!("{:x}", Sha256::digest(bytes)) == VAD_SHA
&& asr["vad_report_sha256"] == VAD_SHA,
"Frozen VAD bytes changed"
);
let vad: Value = serde_json::from_slice(bytes)?;
ensure!(
asr["actual_vad_report"] == vad,
"VAD parsed provenance differs from original bytes"
);
ensure!(
asr["candidate"] == "base"
&& asr["worker_sha256"]
== "12c681b4bcd9c7679b647b9a041e121a2f9e72cb2a9c6f189f529ec8b79c36f8",
"Unknown gated ASR worker/model"
);
for name in ["ffmpeg_sha256", "ffprobe_sha256"] {
ensure!(asr[name] == vad[name], "VAD and ASR decoder differ");
}
Ok(Some(vad))
}
pub fn planned_windows(vad: &Value, row: &Value, asset: &Value) -> Result<Vec<Value>> {
let detected = vad["rows"]
.as_array()
.context("Missing frozen VAD rows")?
.iter()
.find(|v| v["asset"] == row["asset"])
.context("Missing VAD competitor")?;
ensure!(
detected["source_sha256"] == asset["sha256"] && row["actual_probe"] == detected["probe"],
"VAD source/probe differs"
);
let audio = detected["probe"]["streams"]
.as_array()
.context("Missing audio probe")?
.iter()
.find(|v| v["codec_type"] == "audio")
.context("Missing primary audio")?;
let start: f64 = audio["start_time"].as_str().unwrap_or("0").parse()?;
let duration: f64 = audio["duration"]
.as_str()
.context("Missing audio duration")?
.parse()?;
ensure!(
start.is_finite()
&& start >= 0.0
&& duration.is_finite()
&& duration > 0.0
&& duration <= 600.0,
"Invalid source timeline"
);
let origin = (start * 1000.0).ceil() as u64;
let end = ((start + duration) * 1000.0).ceil() as u64;
ensure!(
row["audio_start_ms"] == origin
&& row["audio_end_ms"] == end
&& row["audio_stream_index"] == audio["index"],
"ASR timeline differs from frozen probe"
);
let samples = number(&detected["native"]["samples"])?;
let intervals = detected["intervals"]
.as_array()
.context("Missing VAD intervals")?;
let mut result = Vec::new();
let mut previous = 0;
for (index, interval) in intervals.iter().enumerate() {
let raw_start = number(&interval["raw_cs"][0])?
.checked_mul(160)
.context("Interval overflow")?;
let raw_end = number(&interval["raw_cs"][1])?
.checked_mul(160)
.context("Interval overflow")?;
let first = raw_start.min(samples);
let last = raw_end.min(samples);
ensure!(
raw_end > raw_start
&& first >= previous
&& interval["source_samples"] == json!([first, last])
&& interval["tail_padding_samples"] == raw_end.saturating_sub(samples),
"Invalid bounded VAD intersection"
);
previous = last;
let mut offset = origin + first / 16;
let end = origin + last / 16;
while offset < end {
let duration = (end - offset).min(30000);
result.push(json!({"offset_ms":offset,"duration_ms":duration,"vad_interval_index":index,"vad_source_intersection":interval,"unrepresented_tail_samples":last%16}));
ensure!(result.len() <= 20, "Too many gated windows");
offset += duration;
}
}
ensure!(
row["status"]
== if result.is_empty() {
"no_speech"
} else {
"speech"
}
&& row["asr_not_run"] == result.is_empty(),
"Missing explicit speech/no-speech evidence"
);
Ok(result)
}
pub fn matches_plan(window: &Value, planned: &Value) -> bool {
[
"offset_ms",
"duration_ms",
"vad_interval_index",
"vad_source_intersection",
"unrepresented_tail_samples",
]
.iter()
.all(|key| window[key] == planned[key])
}
pub fn verify_windows(windows: &[Value], planned: &[Value]) -> Result<()> {
ensure!(
windows.len() == planned.len()
&& windows
.iter()
.zip(planned)
.all(|(window, plan)| matches_plan(window, plan)),
"Incomplete or changed VAD interval windows"
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn source(intervals: Value) -> (Value, Value, Value) {
let probe = json!({"streams":[{"codec_type":"audio","index":2,"start_time":"0.125","duration":"65.0"}]});
let vad = json!({"rows":[{"asset":"clip04","source_sha256":"sha","probe":probe,"native":{"samples":1040000},"intervals":intervals}]});
let row = json!({"asset":"clip04","actual_probe":probe,"audio_start_ms":125,"audio_end_ms":65125,"audio_stream_index":2,"status":"speech","asr_not_run":false});
(vad, row, json!({"sha256":"sha"}))
}
#[test]
fn windows_reconstruct_original_intervals_without_merging() {
let intervals = json!([
{"raw_cs":[0,3100],"source_samples":[0,496000],"tail_padding_samples":0},
{"raw_cs":[4000,4200],"source_samples":[640000,672000],"tail_padding_samples":0}
]);
let (vad, row, asset) = source(intervals);
let plan = planned_windows(&vad, &row, &asset).unwrap();
assert_eq!(plan.len(), 3);
assert_eq!(plan[0]["offset_ms"], 125);
assert_eq!(plan[1]["offset_ms"], 30125);
assert_eq!(plan[1]["duration_ms"], 1000);
assert_eq!(plan[2]["offset_ms"], 40125);
assert!(verify_windows(&plan, &plan).is_ok());
assert!(verify_windows(&plan[..2], &plan).is_err());
let mut omitted = plan[2].clone();
omitted["vad_interval_index"] = json!(0);
assert!(!matches_plan(&omitted, &plan[2]));
let mut changed = vad.clone();
changed["rows"][0]["intervals"][0]["source_samples"][1] = json!(480000);
assert!(planned_windows(&changed, &row, &asset).is_err());
}
#[test]
fn no_speech_requires_empty_frozen_intervals_and_explicit_status() {
let (vad, mut row, asset) = source(json!([]));
assert!(planned_windows(&vad, &row, &asset).is_err());
row["status"] = json!("no_speech");
row["asr_not_run"] = json!(true);
assert!(planned_windows(&vad, &row, &asset).unwrap().is_empty());
row["audio_start_ms"] = json!(0);
assert!(planned_windows(&vad, &row, &asset).is_err());
}
#[test]
fn arbitrary_vad_claim_cannot_authorize_candidate_exclusion() {
let report = json!({"policy":POLICY,"vad_report_sha256":VAD_SHA,"vad_report_utf8":"{}","actual_vad_report":{}});
assert!(verified_report(&report).is_err());
assert!(verified_report(&json!({"policy":super::super::POLICY}))
.unwrap()
.is_none());
}
}
+25 -3
View File
@@ -13,6 +13,7 @@ use std::{
const FIXTURE_SHA: &str = "b4e465a0eb9582b826eebd2a20797e1680e4907e336d02d0ef2194e2ba0af393";
const ASSETS: [&str; 5] = ["clip04", "music01", "music02", "sound01", "sound02"];
mod gated;
const POLICY: &str = "primary-audio-range-v3;aresample16000-before-atrim;samplecoverage-1ms;nonoverlapping30s;independent-process-per-window;auto-language;segment-times";
fn digest(path: &Path) -> Result<String> {
@@ -90,7 +91,7 @@ fn eligible(asr: &Value, fixture: &Value) -> Result<(Vec<Value>, Vec<Value>)> {
ensure!(
asr["complete"] == true
&& asr["fixture_sha256"] == FIXTURE_SHA
&& asr["policy"] == POLICY
&& matches!(asr["policy"].as_str(), Some(POLICY) | Some(gated::POLICY))
&& asr["backend"] == "forced CPU"
&& asr["threads"] == 1,
"Incomplete, stale, or unrecognized ASR report"
@@ -104,6 +105,7 @@ fn eligible(asr: &Value, fixture: &Value) -> Result<(Vec<Value>, Vec<Value>)> {
] {
ensure!(asr[field].as_str().is_some_and(|hash| hash.len() == 64 && hash.bytes().all(|b| b.is_ascii_hexdigit())), "Missing ASR provenance");
}
let vad = gated::verified_report(asr)?;
let rows = asr["rows"].as_array().context("Missing ASR rows")?;
ensure!(rows.len() == 5, "All five competing assets required");
let mut seen = BTreeSet::new();
@@ -133,12 +135,23 @@ fn eligible(asr: &Value, fixture: &Value) -> Result<(Vec<Value>, Vec<Value>)> {
);
let windows = row["windows"].as_array().context("Missing windows")?;
ensure!(windows.len() <= 20, "Oversized window list");
let planned = vad
.as_ref()
.map(|vad| gated::planned_windows(vad, row, asset))
.transpose()?;
if let Some(planned) = &planned {
gated::verify_windows(windows, planned)?;
}
let mut next = start;
for (window_index, window) in windows.iter().enumerate() {
let offset = number(&window["offset_ms"])?;
let duration = number(&window["duration_ms"])?;
ensure!(
offset == next && duration == (end - offset).min(30000) && duration > 0,
if let Some(planned) = &planned {
gated::matches_plan(window, &planned[window_index])
} else {
offset == next && duration == (end - offset).min(30000) && duration > 0
},
"Missing, overlapping or truncated source windows"
);
next = offset.checked_add(duration).context("Window overflow")?;
@@ -183,7 +196,10 @@ fn eligible(asr: &Value, fixture: &Value) -> Result<(Vec<Value>, Vec<Value>)> {
ensure!(segments.len() <= 5000, "Too many recognized segments");
}
}
ensure!(next == end, "Incomplete source-window coverage");
ensure!(
planned.is_some() || next == end,
"Incomplete source-window coverage"
);
}
ensure!(
asr["quality_failed"] == !excluded.is_empty(),
@@ -197,6 +213,12 @@ fn eligible(asr: &Value, fixture: &Value) -> Result<(Vec<Value>, Vec<Value>)> {
== excluded.len(),
"ASR validation failure count inconsistent"
);
if vad.is_some() {
ensure!(
asr["comparison_eligible"] == excluded.is_empty(),
"Gated comparison status inconsistent"
);
}
Ok((segments, excluded))
}