Preserve explicit code candidates and expose verified model asset milestones
This commit is contained in:
1 parent
6e05227f53
commit
e28e20af38
9 files changed
+471
-12
No files matched your search
@@ -17,6 +17,31 @@ use std::{
|
|||||||
/// Exact bytes of the pinned text model plus its multimedia projection.
|
/// Exact bytes of the pinned text model plus its multimedia projection.
|
||||||
pub const TOTAL_BYTES: u64 = 864_676_480;
|
pub const TOTAL_BYTES: u64 = 864_676_480;
|
||||||
const ATTEMPTS: usize = 3;
|
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<VerifiedAsset> {
|
||||||
|
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)]
|
#[derive(Deserialize)]
|
||||||
struct Manifest {
|
struct Manifest {
|
||||||
@@ -66,6 +91,7 @@ fn manifest() -> Result<Manifest> {
|
|||||||
if manifest.files[0].path == manifest.files[1].path {
|
if manifest.files[0].path == manifest.files[1].path {
|
||||||
bail!("duplicate model manifest artifact");
|
bail!("duplicate model manifest artifact");
|
||||||
}
|
}
|
||||||
|
validate_artifact_order(&manifest.files)?;
|
||||||
Ok(manifest)
|
Ok(manifest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,28 +184,60 @@ fn verified(path: &Path, artifact: &Artifact) -> Result<bool> {
|
|||||||
/// is completed verified bytes plus bytes of the current attempt. On failure it
|
/// is completed verified bytes plus bytes of the current attempt. On failure it
|
||||||
/// resets to verified completed bytes; cached files never trigger network access.
|
/// resets to verified completed bytes; cached files never trigger network access.
|
||||||
pub fn provision(directory: &Path, progress: &AtomicU64) -> Result<()> {
|
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 manifest = manifest()?;
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
.connect_timeout(Duration::from_secs(20))
|
.connect_timeout(Duration::from_secs(20))
|
||||||
.timeout(Duration::from_secs(900))
|
.timeout(Duration::from_secs(900))
|
||||||
.user_agent(concat!("Searchi/", env!("CARGO_PKG_VERSION")))
|
.user_agent(concat!("Searchi/", env!("CARGO_PKG_VERSION")))
|
||||||
.build()?;
|
.build()?;
|
||||||
provision_with(directory, progress, &manifest, &client, |artifact| {
|
provision_with_callback(
|
||||||
|
directory,
|
||||||
|
progress,
|
||||||
|
&manifest,
|
||||||
|
&client,
|
||||||
|
|artifact| {
|
||||||
format!(
|
format!(
|
||||||
"https://huggingface.co/{}/resolve/{}/{}",
|
"https://huggingface.co/{}/resolve/{}/{}",
|
||||||
manifest.export_repository, manifest.revision, artifact.path
|
manifest.export_repository, manifest.revision, artifact.path
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
on_verified,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL injection is private so tests can use a local HTTP fixture. Production
|
// URL injection is private so tests can use a local HTTP fixture. Production
|
||||||
// exposes no arbitrary download source or unverified executable provisioner.
|
// exposes no arbitrary download source or unverified executable provisioner.
|
||||||
|
#[cfg(test)]
|
||||||
fn provision_with(
|
fn provision_with(
|
||||||
directory: &Path,
|
directory: &Path,
|
||||||
progress: &AtomicU64,
|
progress: &AtomicU64,
|
||||||
manifest: &Manifest,
|
manifest: &Manifest,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
url: impl Fn(&Artifact) -> String,
|
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<()> {
|
) -> Result<()> {
|
||||||
fs::create_dir_all(directory).context("create model cache directory")?;
|
fs::create_dir_all(directory).context("create model cache directory")?;
|
||||||
progress.store(0, Ordering::Release);
|
progress.store(0, Ordering::Release);
|
||||||
@@ -190,6 +248,9 @@ fn provision_with(
|
|||||||
if verified(&destination, artifact)? {
|
if verified(&destination, artifact)? {
|
||||||
completed += artifact.bytes;
|
completed += artifact.bytes;
|
||||||
progress.store(completed, Ordering::Release);
|
progress.store(completed, Ordering::Release);
|
||||||
|
if let Some(asset) = verified_asset(artifact) {
|
||||||
|
on_verified(asset);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut last_error = None;
|
let mut last_error = None;
|
||||||
@@ -225,6 +286,9 @@ fn provision_with(
|
|||||||
}
|
}
|
||||||
completed += artifact.bytes;
|
completed += artifact.bytes;
|
||||||
progress.store(completed, Ordering::Release);
|
progress.store(completed, Ordering::Release);
|
||||||
|
if let Some(asset) = verified_asset(artifact) {
|
||||||
|
on_verified(asset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -564,4 +628,238 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.ends_with("models/embeddinggemma2"));
|
.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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ impl SemanticOrigin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A conservative intent hint, not a language understanding model. Explicit
|
/// 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.
|
/// silently restrict modality. Word boundaries avoid filename substrings.
|
||||||
fn preferred_origin(query: &str) -> Option<SemanticOrigin> {
|
fn preferred_origin(query: &str) -> Option<SemanticOrigin> {
|
||||||
if query.split_whitespace().count() == 1 && Path::new(query).extension().is_some() {
|
if query.split_whitespace().count() == 1 && Path::new(query).extension().is_some() {
|
||||||
@@ -49,6 +49,12 @@ fn preferred_origin(query: &str) -> Option<SemanticOrigin> {
|
|||||||
"screenshot",
|
"screenshot",
|
||||||
"dokument",
|
"dokument",
|
||||||
"textdatei",
|
"textdatei",
|
||||||
|
"code",
|
||||||
|
"function",
|
||||||
|
"implementation",
|
||||||
|
"quellcode",
|
||||||
|
"funktion",
|
||||||
|
"implementierung",
|
||||||
]) {
|
]) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -211,6 +217,12 @@ mod tests {
|
|||||||
"a document that someone mentioned",
|
"a document that someone mentioned",
|
||||||
"speech.png",
|
"speech.png",
|
||||||
"a screenshot of an audio waveform",
|
"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}");
|
assert_eq!(preferred_origin(query), None, "{query}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
@@ -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
|
ended the app group with SIGTERM and Xvfb with status zero. Subsequent process
|
||||||
inspection found no remaining app/worker/WebKit/Xvfb/DBus/clipboard helpers,
|
inspection found no remaining app/worker/WebKit/Xvfb/DBus/clipboard helpers,
|
||||||
and the outer `/dev/dxg` remained the real device (hex a:102).
|
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.
|
||||||
@@ -105,8 +105,10 @@ restart or substituted model was used. Reproduce with the
|
|||||||
|
|
||||||
## Open quality gates
|
## Open quality gates
|
||||||
|
|
||||||
- Correct the documented All-files audio-intent suppression without hiding
|
- Validate the subsequent explicit-code routing fix on broader mixed queries
|
||||||
legitimate media results; validate candidate preservation before ranking.
|
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,
|
- Evaluate general/code query task fusion on a new independently labelled set,
|
||||||
with bounded retrieval and measured cache/filter costs.
|
with bounded retrieval and measured cache/filter costs.
|
||||||
- Improve music discrimination and precise speech timestamps.
|
- Improve music discrimination and precise speech timestamps.
|
||||||
|
|||||||
@@ -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
|
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
|
allocation/parity, pressure behavior, long code queries, representative query
|
||||||
latency and updated installed desktop packaging remain release checks.
|
latency and updated installed desktop packaging remain release checks.
|
||||||
@@ -55,3 +55,25 @@ The existing `semantic.eligibility` and
|
|||||||
`semantic.eligibility_filtered_scan_and_hash` traces expose eligibility cost.
|
`semantic.eligibility_filtered_scan_and_hash` traces expose eligibility cost.
|
||||||
Use an independently reviewed held-out corpus for acceptance; the frozen
|
Use an independently reviewed held-out corpus for acceptance; the frozen
|
||||||
diagnostic alone must not become the optimization target.
|
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.
|
||||||
+9
-1
@@ -186,9 +186,15 @@ Audit and measured priorities: [EMBEDDINGGEMMA2-AUDIT.md](EMBEDDINGGEMMA2-AUDIT.
|
|||||||
integration and actual Windows CPU parity/boundary/reference tests.
|
integration and actual Windows CPU parity/boundary/reference tests.
|
||||||
Paired samples reduced private commitment by 183 MB; resident memory and
|
Paired samples reduced private commitment by 183 MB; resident memory and
|
||||||
cached startup were essentially unchanged. See [QUERY-CONTEXT.md](QUERY-CONTEXT.md).
|
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
|
- [ ] Evaluate text readiness before the larger media projector finishes
|
||||||
downloading; preserve verified assets and honest media setup status.
|
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.
|
- [ ] Buffered bounded media IPC and linear UTF-8 source-window offset processing.
|
||||||
- [ ] Evaluate 256d MRL and content-specific vision budgets on independent data.
|
- [ ] Evaluate 256d MRL and content-specific vision budgets on independent data.
|
||||||
- [ ] Validate GPU precision, synchronization-aware timings, parity and fallback
|
- [ ] 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).
|
returned candidates. See [mixed quality](MIXED-QUALITY.md).
|
||||||
- [ ] Measure task fusion and cache cost against independently reviewed
|
- [ ] Measure task fusion and cache cost against independently reviewed
|
||||||
held-out data, preserving the frozen diagnostic for regression comparison.
|
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
|
- [ ] Validate code search UI interaction on native Windows/macOS/Linux and
|
||||||
actual GPU hardware. Tiny CPU fixtures do not close those release gates.
|
actual GPU hardware. Tiny CPU fixtures do not close those release gates.
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
A bounded [CPU diagnostic harness](../tools/speech-eval/README.md) is now prepared.
|
||||||
Seven structural tests passed on Windows, including actual owned subprocess
|
Seven structural tests passed on Windows, including actual owned subprocess
|
||||||
failure, timeout, output overflow and retained diagnostics. No ASR model has been
|
failure, timeout, output overflow and retained diagnostics. Both candidate models
|
||||||
downloaded or run for this proof; these tests establish no speech quality.
|
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
|
## 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
|
Use immutable download URLs of the form
|
||||||
`https://huggingface.co/ggerganov/whisper.cpp/resolve/5359861c739e955e79d9a303bcbc70fb988958b1/ggml-base.bin`.
|
`https://huggingface.co/ggerganov/whisper.cpp/resolve/5359861c739e955e79d9a303bcbc70fb988958b1/ggml-base.bin`.
|
||||||
These are **publisher metadata**, not hashes verified from downloaded bytes yet.
|
Both downloads were independently checked against these exact bytes/SHA values
|
||||||
Commit a manifest only after a genuine download verifies exact size/SHA, model
|
on 2026-10-13. The clean runtime checkout resolves to the pinned commit; model
|
||||||
loading and provenance. Never accept upstream `for-tests-*` empty models.
|
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),
|
[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).
|
[blob metadata](https://huggingface.co/api/models/ggerganov/whisper.cpp?blobs=true).
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user