diff --git a/crates/searchi-assets/src/lib.rs b/crates/searchi-assets/src/lib.rs index 56f1c15..cde9641 100644 --- a/crates/searchi-assets/src/lib.rs +++ b/crates/searchi-assets/src/lib.rs @@ -17,6 +17,31 @@ use std::{ /// Exact bytes of the pinned text model plus its multimedia projection. pub const TOTAL_BYTES: u64 = 864_676_480; const ATTEMPTS: usize = 3; +const BACKBONE_FILE: &str = "embeddinggemma-2-Q8_0.gguf"; +const PROJECTOR_FILE: &str = "mmproj-embeddinggemma-2-Q8_0.gguf"; + +/// One verified pinned artifact. This does not imply the full bundle or an +/// inference session is ready; a later artifact can still fail provisioning. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VerifiedAsset { + TextBackbone, + MediaProjector, +} + +fn verified_asset(artifact: &Artifact) -> Option { + match artifact.path.as_str() { + BACKBONE_FILE => Some(VerifiedAsset::TextBackbone), + PROJECTOR_FILE => Some(VerifiedAsset::MediaProjector), + _ => None, // Private HTTP fixtures may use arbitrary artifact names. + } +} + +fn validate_artifact_order(files: &[Artifact]) -> Result<()> { + if files.len() != 2 || files[0].path != BACKBONE_FILE || files[1].path != PROJECTOR_FILE { + bail!("bundled model manifest must provision the text backbone before the media projector"); + } + Ok(()) +} #[derive(Deserialize)] struct Manifest { @@ -66,6 +91,7 @@ fn manifest() -> Result { if manifest.files[0].path == manifest.files[1].path { bail!("duplicate model manifest artifact"); } + validate_artifact_order(&manifest.files)?; Ok(manifest) } @@ -158,28 +184,60 @@ fn verified(path: &Path, artifact: &Artifact) -> Result { /// is completed verified bytes plus bytes of the current attempt. On failure it /// resets to verified completed bytes; cached files never trigger network access. pub fn provision(directory: &Path, progress: &AtomicU64) -> Result<()> { + provision_with_verified_assets(directory, progress, |_| {}) +} + +/// Provision the complete pinned pair, notifying after each artifact's exact +/// cached SHA verification or successful verified atomic installation. The +/// callback is synchronous and has no error return: callers must keep their +/// inference/setup errors separate from download errors. Byte progress alone +/// never triggers a notification. Success still requires both artifacts. +pub fn provision_with_verified_assets( + directory: &Path, + progress: &AtomicU64, + on_verified: impl FnMut(VerifiedAsset), +) -> Result<()> { let manifest = manifest()?; let client = Client::builder() .connect_timeout(Duration::from_secs(20)) .timeout(Duration::from_secs(900)) .user_agent(concat!("Searchi/", env!("CARGO_PKG_VERSION"))) .build()?; - provision_with(directory, progress, &manifest, &client, |artifact| { - format!( - "https://huggingface.co/{}/resolve/{}/{}", - manifest.export_repository, manifest.revision, artifact.path - ) - }) + provision_with_callback( + directory, + progress, + &manifest, + &client, + |artifact| { + format!( + "https://huggingface.co/{}/resolve/{}/{}", + manifest.export_repository, manifest.revision, artifact.path + ) + }, + on_verified, + ) } // URL injection is private so tests can use a local HTTP fixture. Production // exposes no arbitrary download source or unverified executable provisioner. +#[cfg(test)] fn provision_with( directory: &Path, progress: &AtomicU64, manifest: &Manifest, client: &Client, url: impl Fn(&Artifact) -> String, +) -> Result<()> { + provision_with_callback(directory, progress, manifest, client, url, |_| {}) +} + +fn provision_with_callback( + directory: &Path, + progress: &AtomicU64, + manifest: &Manifest, + client: &Client, + url: impl Fn(&Artifact) -> String, + mut on_verified: impl FnMut(VerifiedAsset), ) -> Result<()> { fs::create_dir_all(directory).context("create model cache directory")?; progress.store(0, Ordering::Release); @@ -190,6 +248,9 @@ fn provision_with( if verified(&destination, artifact)? { completed += artifact.bytes; progress.store(completed, Ordering::Release); + if let Some(asset) = verified_asset(artifact) { + on_verified(asset); + } continue; } let mut last_error = None; @@ -225,6 +286,9 @@ fn provision_with( } completed += artifact.bytes; progress.store(completed, Ordering::Release); + if let Some(asset) = verified_asset(artifact) { + on_verified(asset); + } } Ok(()) } @@ -564,4 +628,238 @@ mod tests { .unwrap() .ends_with("models/embeddinggemma2")); } + #[test] + fn pinned_backbone_must_precede_projector() { + let mut files = manifest().unwrap().files; + validate_artifact_order(&files).unwrap(); + files.swap(0, 1); + assert!(validate_artifact_order(&files).is_err()); + } + + #[test] + fn cached_pair_emits_verified_callbacks_without_network() { + let directory = tempfile::tempdir().unwrap(); + let manifest = test_manifest(vec![ + artifact(BACKBONE_FILE, b"backbone"), + artifact(PROJECTOR_FILE, b"projection"), + ]); + fs::write(directory.path().join(BACKBONE_FILE), b"backbone").unwrap(); + fs::write(directory.path().join(PROJECTOR_FILE), b"projection").unwrap(); + let progress = AtomicU64::new(0); + let mut observed = Vec::new(); + provision_with_callback( + directory.path(), + &progress, + &manifest, + &client(), + |_| panic!("Cached pair attempted network"), + |asset| { + observed.push(asset); + let bytes = match asset { + VerifiedAsset::TextBackbone => 8, + VerifiedAsset::MediaProjector => 18, + }; + assert_eq!(progress.load(Ordering::Acquire), bytes); + }, + ) + .unwrap(); + assert_eq!( + observed, + [VerifiedAsset::TextBackbone, VerifiedAsset::MediaProjector] + ); + assert_eq!(progress.load(Ordering::Acquire), 18); + } + + #[test] + fn failed_projector_retains_verified_text_callback_and_progress() { + let directory = tempfile::tempdir().unwrap(); + fs::write(directory.path().join(PROJECTOR_FILE), b"oldproject").unwrap(); + let manifest = test_manifest(vec![ + artifact(BACKBONE_FILE, b"backbone"), + artifact(PROJECTOR_FILE, b"projection"), + ]); + let mut responses = vec![response(b"backbone", Some(8))]; + responses.extend(vec![response(b"corrupted!", Some(10)); ATTEMPTS]); + let (url, server) = server(responses); + let progress = AtomicU64::new(0); + let mut observed = Vec::new(); + let result = provision_with_callback( + directory.path(), + &progress, + &manifest, + &client(), + |_| url.clone(), + |asset| { + assert_eq!(asset, VerifiedAsset::TextBackbone); + assert_eq!( + fs::read(directory.path().join(BACKBONE_FILE)).unwrap(), + b"backbone" + ); + observed.push(asset); + }, + ); + assert!( + result.is_err(), + "The incomplete bundle must not be successful" + ); + assert_eq!(server.join().unwrap(), ATTEMPTS + 1); + assert_eq!(observed, [VerifiedAsset::TextBackbone]); + assert_eq!(progress.load(Ordering::Acquire), 8); + assert_eq!( + fs::read(directory.path().join(PROJECTOR_FILE)).unwrap(), + b"oldproject" + ); + assert!(!verified(&directory.path().join(PROJECTOR_FILE), &manifest.files[1]).unwrap()); + assert_no_part_files(directory.path()); + } + + #[test] + fn corrupt_backbone_never_emits_a_verified_callback() { + let directory = tempfile::tempdir().unwrap(); + fs::write(directory.path().join(BACKBONE_FILE), b"corrupt!").unwrap(); + let manifest = test_manifest(vec![ + artifact(BACKBONE_FILE, b"backbone"), + artifact(PROJECTOR_FILE, b"projection"), + ]); + let (url, server) = server(vec![response(b"corrupt!", Some(8)); ATTEMPTS]); + let progress = AtomicU64::new(0); + let mut observed = Vec::new(); + assert!(provision_with_callback( + directory.path(), + &progress, + &manifest, + &client(), + |_| url.clone(), + |asset| observed.push(asset) + ) + .is_err()); + assert_eq!(server.join().unwrap(), ATTEMPTS); + assert!(observed.is_empty()); + assert_eq!(progress.load(Ordering::Acquire), 0); + assert!(!directory.path().join(PROJECTOR_FILE).exists()); + assert_no_part_files(directory.path()); + } + + #[test] + fn partial_backbone_download_never_emits_a_verified_callback() { + let directory = tempfile::tempdir().unwrap(); + let manifest = test_manifest(vec![ + artifact(BACKBONE_FILE, b"backbone"), + artifact(PROJECTOR_FILE, b"projection"), + ]); + let (url, server) = server(vec![response(b"back", Some(8)); ATTEMPTS]); + let progress = AtomicU64::new(0); + let mut observed = Vec::new(); + assert!(provision_with_callback( + directory.path(), + &progress, + &manifest, + &client(), + |_| url.clone(), + |asset| observed.push(asset) + ) + .is_err()); + assert_eq!(server.join().unwrap(), ATTEMPTS); + assert!(observed.is_empty()); + assert_eq!(progress.load(Ordering::Acquire), 0); + assert!(!directory.path().join(BACKBONE_FILE).exists()); + assert_no_part_files(directory.path()); + } + + #[test] + fn text_callback_precedes_gated_projector_and_full_completion() { + use std::sync::mpsc; + let directory = tempfile::tempdir().unwrap(); + fs::write(directory.path().join(BACKBONE_FILE), b"backbone").unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let url = format!("http://{}/fixture", listener.local_addr().unwrap()); + let (release, wait) = mpsc::channel(); + let (headers_sent, headers) = mpsc::channel(); + listener.set_nonblocking(true).unwrap(); + let server = thread::spawn(move || { + let deadline = Instant::now() + Duration::from_secs(5); + let mut stream = loop { + match listener.accept() { + Ok((stream, _)) => break stream, + Err(error) + if error.kind() == std::io::ErrorKind::WouldBlock + && Instant::now() < deadline => + { + thread::sleep(Duration::from_millis(2)) + } + Err(error) => panic!("Gated local fixture accept failed: {error}"), + } + }; + stream.set_nonblocking(false).unwrap(); + stream + .set_read_timeout(Some(Duration::from_secs(2))) + .unwrap(); + let mut request = Vec::new(); + let mut buffer = [0u8; 1024]; + while !request.windows(4).any(|w| w == b"\r\n\r\n") { + let n = stream.read(&mut buffer).unwrap(); + assert!(n > 0); + request.extend_from_slice(&buffer[..n]); + assert!(request.len() <= 8192); + } + stream + .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 10\r\nConnection: close\r\n\r\n") + .unwrap(); + headers_sent.send(()).unwrap(); + wait.recv_timeout(Duration::from_secs(2)).unwrap(); + stream.write_all(b"projection").unwrap(); + }); + let (events, observed) = mpsc::channel(); + let (done, completion) = mpsc::channel(); + let path = directory.path().to_path_buf(); + let provisioning = thread::spawn(move || { + let manifest = test_manifest(vec![ + artifact(BACKBONE_FILE, b"backbone"), + artifact(PROJECTOR_FILE, b"projection"), + ]); + let progress = AtomicU64::new(0); + let result = provision_with_callback( + &path, + &progress, + &manifest, + &client(), + |_| url.clone(), + |asset| { + let artifact = match asset { + VerifiedAsset::TextBackbone => &manifest.files[0], + VerifiedAsset::MediaProjector => &manifest.files[1], + }; + assert!(verified(&path.join(&artifact.path), artifact).unwrap()); + events.send(asset).unwrap(); + }, + ); + done.send(result).unwrap(); + }); + assert_eq!( + observed.recv_timeout(Duration::from_secs(2)).unwrap(), + VerifiedAsset::TextBackbone + ); + headers.recv_timeout(Duration::from_secs(2)).unwrap(); + assert!( + observed.try_recv().is_err(), + "Projector is not yet verified" + ); + assert!( + completion.try_recv().is_err(), + "Bundle must remain incomplete" + ); + assert!(!directory.path().join(PROJECTOR_FILE).exists()); + release.send(()).unwrap(); + assert_eq!( + observed.recv_timeout(Duration::from_secs(2)).unwrap(), + VerifiedAsset::MediaProjector + ); + completion + .recv_timeout(Duration::from_secs(2)) + .unwrap() + .unwrap(); + provisioning.join().unwrap(); + server.join().unwrap(); + assert_no_part_files(directory.path()); + } } diff --git a/crates/searchi-service/src/routing.rs b/crates/searchi-service/src/routing.rs index 2173b5c..74aa55d 100644 --- a/crates/searchi-service/src/routing.rs +++ b/crates/searchi-service/src/routing.rs @@ -22,7 +22,7 @@ impl SemanticOrigin { } /// A conservative intent hint, not a language understanding model. Explicit -/// document/visual cues veto it; ordinary topic and instrument queries do not +/// document/visual/code cues veto it; ordinary topic and instrument queries do not /// silently restrict modality. Word boundaries avoid filename substrings. fn preferred_origin(query: &str) -> Option { if query.split_whitespace().count() == 1 && Path::new(query).extension().is_some() { @@ -49,6 +49,12 @@ fn preferred_origin(query: &str) -> Option { "screenshot", "dokument", "textdatei", + "code", + "function", + "implementation", + "quellcode", + "funktion", + "implementierung", ]) { return None; } @@ -211,6 +217,12 @@ mod tests { "a document that someone mentioned", "speech.png", "a screenshot of an audio waveform", + "code that chooses audio search when a query mentions speech", + "an implementation for identifying music", + "a function that decodes audio samples", + "Quellcode für die Suche nach gesprochenen Themen", + "eine Funktion für die Verarbeitung von Musik", + "Implementierung für die Erkennung von Geräuschen", ] { assert_eq!(preferred_origin(query), None, "{query}"); } diff --git a/crates/searchi-service/tests/routing_google_cpu.rs b/crates/searchi-service/tests/routing_google_cpu.rs new file mode 100644 index 0000000..60059d0 --- /dev/null +++ b/crates/searchi-service/tests/routing_google_cpu.rs @@ -0,0 +1,56 @@ +use anyhow::Result; +use searchi_core::SearchIndex; +use searchi_service::{FfmpegTools, HybridSearch, MediaLimits, SearchOptions, SemanticOrigin}; +use std::{fs, path::PathBuf}; + +#[test] +#[ignore = "Requires verified Google CPU worker/model and FFmpeg fixtures"] +fn explicit_code_intent_preserves_code_among_real_audio_candidates() -> Result<()> { + assert_eq!(std::env::var("SEARCHI_FORCE_CPU")?, "1"); + let model = std::env::var_os("SEARCHI_GEMMA_MODEL_DIR").expect("Set model directory"); + let worker = std::env::var_os("SEARCHI_GEMMA_WORKER").expect("Set worker path"); + let tools = FfmpegTools { + ffmpeg: std::env::var_os("SEARCHI_TEST_FFMPEG") + .expect("Set FFmpeg") + .into(), + ffprobe: std::env::var_os("SEARCHI_TEST_FFPROBE") + .expect("Set FFprobe") + .into(), + }; + let fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../fixtures"); + let temp = tempfile::tempdir()?; + let corpus = temp.path().join("corpus"); + fs::create_dir(&corpus)?; + // Preserve the unchanged diagnostic implementation and actual licensed sound. + fs::copy( + fixture.join("mixed/sources/crates__searchi-service__src__routing.rs"), + corpus.join("routing.rs"), + )?; + fs::copy( + fixture.join("media/assets/sound02.mp3"), + corpus.join("sound.mp3"), + )?; + let database = temp.path().join("index.db"); + let mut lexical = SearchIndex::open(&database)?; + lexical.index_folder(&corpus)?; + let mut indexer = HybridSearch::open(&database, &model, &worker, 1)?; + let report = indexer.index_embeddings_with_media(&tools, &MediaLimits::default())?; + assert!(report.failures.is_empty(), "{report:?}"); + assert_eq!(report.media_files, 1); + drop(indexer); + let mut search = HybridSearch::open_query(&database, &model, &worker, 1)?; + let query = "code that chooses audio search when a query mentions speech"; + let options = SearchOptions::default(); + let audio = search.search_semantic_with_origin(query, 20, &options, SemanticOrigin::Audio)?; + assert!( + !audio.is_empty(), + "The old exclusive audio lane must qualify" + ); + assert!(audio.iter().all(|hit| hit.filename == "sound.mp3")); + let hits = search.search_semantic(query, 20, &options)?; + assert!( + hits.iter().any(|hit| hit.filename == "routing.rs"), + "Explicit code intent lost its semantic implementation: {hits:?}" + ); + Ok(()) +} diff --git a/docs/LINUX-DESKTOP-VALIDATION.md b/docs/LINUX-DESKTOP-VALIDATION.md index 5e6ccf8..46c8b6f 100644 --- a/docs/LINUX-DESKTOP-VALIDATION.md +++ b/docs/LINUX-DESKTOP-VALIDATION.md @@ -219,3 +219,56 @@ process swap; their private `/dev/dxg` was the null device (1:3). Owned shutdown ended the app group with SIGTERM and Xvfb with status zero. Subsequent process inspection found no remaining app/worker/WebKit/Xvfb/DBus/clipboard helpers, and the outer `/dev/dxg` remained the real device (hex a:102). + +## Native query-role validation: `5d9cfc4` + +Executed on the same provisioned Ubuntu 24.04 x86-64 WSL host. The isolated +normal `auto` worker was rebuilt with one job, retaining Vulkan and CPU modules. +All inference used `SEARCHI_FORCE_CPU=1`; the benchmark additionally requested +`--device cpu` and checked that no Vulkan module loaded. Windows staged resources +were not changed. Worker SHA-256: +`b52d50a1e676bd4bf64124daff1c4ce83d86a8bde01e4eaf09838cbb24a4c28f`. + +Actual Google checks passed: + +- `query_context_cpu`: one passed in **42.88 s**, covering query role/context + negotiation, unchanged real vectors/ranking, multilingual CodeRetrieval, + exact 512-token boundary, nonfatal oversized rejection, indexing/media guards + and full-context document windows. Compilation took 1m 06s. +- CLI/MCP `actual_google_cli_and_mcp_use_query_role_with_cited_results`: one + passed in **38.73 s**, including real indexing with the full role, foreground + CLI search, retained MCP query role/context and guarded source excerpts. + +Logs remain in the native cache as `query-role-native-build.log`, +`query-context-linux-test.log`, and `query-context-linux-mcp.log`. These tiny +fixtures establish native role/client correctness, not representative relevance +or a release package gate. + +After correctness tests and other agents' downloads were terminal, a paired +benchmark ran six fresh workers, alternating order, three per role, one inference +thread. Cached model bytes were verified once in **241.48 ms** before process +trials; process startup excludes that verification and desktop preparation. + +| Median observed measurement | Indexing 2048 | Query 512 | +| --- | ---: | ---: | +| Startup to ready | 717.08 ms | 564.02 ms | +| First query IPC/inference | 67.13 ms | 66.89 ms | +| Ready PSS | 405,303,296 bytes | 405,278,720 bytes | +| Ready RSS | 406,806,528 bytes | 406,773,760 bytes | +| Ready private dirty resident pages | 103,739,392 bytes | 103,735,296 bytes | +| After query PSS | 410,157,056 bytes | 410,001,408 bytes | + +Linux resident/PSS observations were effectively unchanged. They must not be +reported as the Windows private commitment saving; this benchmark does not +measure Linux virtual commitment. The startup samples are small cached-process +observations, not cold disk, actual first UI input, pressure behavior or warm p95 +proof. Mapped model pages contribute to resident/PSS figures; do not sum them +as independent model allocations across workers. Raw paired samples are retained +locally in `.searchi/qa/query-context-linux.json`; native benchmark output is +`query-context-linux-benchmark.log`. The benchmark itself also checked nonfatal +rejection of a direct indexing operation followed by a successful code query. + +All owned test and benchmark handles ended successfully. Subsequent process +inspection found no surviving Google workers, Cargo or Rust compiler processes. +Native desktop packaging with this new worker, actual GPU, macOS and +representative performance remain open. diff --git a/docs/MIXED-QUALITY.md b/docs/MIXED-QUALITY.md index e6e2266..7c06262 100644 --- a/docs/MIXED-QUALITY.md +++ b/docs/MIXED-QUALITY.md @@ -105,8 +105,10 @@ restart or substituted model was used. Reproduce with the ## Open quality gates -- Correct the documented All-files audio-intent suppression without hiding - legitimate media results; validate candidate preservation before ranking. +- Validate the subsequent explicit-code routing fix on broader mixed queries + without hiding legitimate media results. A tiny real-model candidate-preservation + regression passed; see [routing evidence](RETRIEVAL-ROUTING.md). The frozen + baseline recorded here remains the pre-fix measurement. - Evaluate general/code query task fusion on a new independently labelled set, with bounded retrieval and measured cache/filter costs. - Improve music discrimination and precise speech timestamps. diff --git a/docs/QUERY-CONTEXT.md b/docs/QUERY-CONTEXT.md index 2590c7f..6c51e13 100644 --- a/docs/QUERY-CONTEXT.md +++ b/docs/QUERY-CONTEXT.md @@ -87,6 +87,9 @@ python tools/embeddinggemma2/query_context_benchmark.py --worker "$env:SEARCHI_G ``` The benchmark reports Windows private commitment/working set or Linux -PSS/private resident pages separately. Native Linux/macOS and actual GPU context +PSS/private resident pages separately. Actual native Linux query/client checks +and six paired measurements also passed; see +[Linux evidence](LINUX-DESKTOP-VALIDATION.md). Linux resident/PSS memory was +effectively unchanged in those samples. Native macOS and actual GPU context allocation/parity, pressure behavior, long code queries, representative query latency and updated installed desktop packaging remain release checks. diff --git a/docs/RETRIEVAL-ROUTING.md b/docs/RETRIEVAL-ROUTING.md index 1b54c28..c1005e9 100644 --- a/docs/RETRIEVAL-ROUTING.md +++ b/docs/RETRIEVAL-ROUTING.md @@ -55,3 +55,25 @@ The existing `semantic.eligibility` and `semantic.eligibility_filtered_scan_and_hash` traces expose eligibility cost. Use an independently reviewed held-out corpus for acceptance; the frozen diagnostic alone must not become the optimization target. + +## Narrow suppression fix and actual regression + +After the frozen baseline confirmed the English speech-related code failure, +explicit code/function/implementation cues now veto automatic audio preference, +alongside the existing document/image cues. English and German cues are covered +by the routing unit test. This changes candidate eligibility through routing; +it does not add CodeRetrieval to All files or implement soft multi-lane fusion. + +The ignored integration `routing_google_cpu` passed on Windows CPU in **38.40 s** +using the verified normal worker and Google model, unchanged frozen routing +source bytes and the licensed real roaring-creature sound. The same remembered +code query produced qualifying audio candidates through the explicit Audio +diagnostic lane. Updated default semantic routing also returned the source +implementation, proving the exclusive suppression is removed in this fixture. +The test asserts candidate preservation, not top-1 superiority or broad quality. +The full mixed baseline above remains the unchanged pre-fix measurement. + +With this fix and the verified-asset callback, the Windows CPU workspace suite +passed 130 tests (23 explicitly ignored integration/resource checks), desktop +Rust passed 28, and both strict all-target Clippy checks and formatting passed. +The ignored actual routing regression was executed separately as described above. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 96c18ed..f7d281e 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -186,9 +186,15 @@ Audit and measured priorities: [EMBEDDINGGEMMA2-AUDIT.md](EMBEDDINGGEMMA2-AUDIT. integration and actual Windows CPU parity/boundary/reference tests. Paired samples reduced private commitment by 183 MB; resident memory and cached startup were essentially unchanged. See [QUERY-CONTEXT.md](QUERY-CONTEXT.md). - - [ ] Verify native Linux/macOS, GPU parity and updated installed packages. + - [x] Native Linux query-context and CLI/MCP cited-result checks, with six + paired CPU process measurements; resident/PSS memory effectively unchanged. + - [ ] Verify native macOS, GPU parity and updated installed packages. - [ ] Evaluate text readiness before the larger media projector finishes downloading; preserve verified assets and honest media setup status. + - [x] Typed backbone/projector verification callbacks with full-pair setup + semantics preserved; 16 asset tests passed, including gated media download. + - [ ] Integrate explicit text/media coverage, retained query readiness and + media retry status across engine/desktop; verify actual first use. - [ ] Buffered bounded media IPC and linear UTF-8 source-window offset processing. - [ ] Evaluate 256d MRL and content-specific vision budgets on independent data. - [ ] Validate GPU precision, synchronization-aware timings, parity and fallback @@ -219,6 +225,8 @@ Completion requires recorded evidence; targets are not measured results. returned candidates. See [mixed quality](MIXED-QUALITY.md). - [ ] Measure task fusion and cache cost against independently reviewed held-out data, preserving the frozen diagnostic for regression comparison. + - [x] Explicit English/German code intent vetoes exclusive audio routing; + actual Google CPU regression preserves code among qualifying audio candidates. - [ ] Validate code search UI interaction on native Windows/macOS/Linux and actual GPU hardware. Tiny CPU fixtures do not close those release gates. diff --git a/docs/SPEECH-ALIGNMENT.md b/docs/SPEECH-ALIGNMENT.md index 13d151c..7b25b4c 100644 --- a/docs/SPEECH-ALIGNMENT.md +++ b/docs/SPEECH-ALIGNMENT.md @@ -6,8 +6,9 @@ Google EmbeddingGemma 2 remains the production semantic model. A bounded [CPU diagnostic harness](../tools/speech-eval/README.md) is now prepared. Seven structural tests passed on Windows, including actual owned subprocess -failure, timeout, output overflow and retained diagnostics. No ASR model has been -downloaded or run for this proof; these tests establish no speech quality. +failure, timeout, output overflow and retained diagnostics. Both candidate models +have now been downloaded and their exact bytes/SHA verified. Neither has been +loaded or evaluated; these tests establish no speech quality. ## Why add a transcript lane @@ -60,9 +61,13 @@ Model metadata was read from the Hugging Face blob API at repository revision Use immutable download URLs of the form `https://huggingface.co/ggerganov/whisper.cpp/resolve/5359861c739e955e79d9a303bcbc70fb988958b1/ggml-base.bin`. -These are **publisher metadata**, not hashes verified from downloaded bytes yet. -Commit a manifest only after a genuine download verifies exact size/SHA, model -loading and provenance. Never accept upstream `for-tests-*` empty models. +Both downloads were independently checked against these exact bytes/SHA values +on 2026-10-13. The clean runtime checkout resolves to the pinned commit; model +URLs, verified hashes and MIT notices are retained under ignored +`.searchi/speech/provisioning.json`. This is provisioning evidence only: no +runtime build, model loading or inference has yet been performed. Commit an +accepted distribution manifest only after model loading and runtime provenance +checks. Never accept upstream `for-tests-*` empty models. [Upstream model provenance](https://github.com/ggml-org/whisper.cpp/blob/master/models/README.md), [blob metadata](https://huggingface.co/api/models/ggerganov/whisper.cpp?blobs=true).