Verify genuine Google music constraints and separate metadata cache revisions

This commit is contained in:
karl committed 2026-10-13 18:42:28 +02:00
1 parent b002399217
commit ce642f1594
7 files changed
+339 -12

No files matched your search

+7
View File
@@ -39,6 +39,13 @@ sound.
Use **All files** to narrow results by folder, file extension, modification
date, file size or media duration. Choose a result to inspect it before opening.
The current source also offers **Artist (exact)** and **Track title (exact)**
filters for music. Enter the complete tag, with or without a text query. These
filters ignore letter case and surrounding spaces; files without the matching
music tag stay out of the results. Existing indexes need a media indexing pass
before these new filters can use their tags. This improvement is not yet in the
v0.1.4 downloads.
### Find the implementation
Choose **Code** in the **All files** filters to search source files. Look up a
+2 -1
View File
@@ -44,6 +44,7 @@ unchanged-vector reuse and production platform checks remain release gates.
Exact artist/title constraints use the core's normalized structured probe keys
before ANN limits and in the eligibility cache key. Metadata-only writes advance
the corpus revision and dirty path so warmed readers cannot retain old eligibility.
a separate durable metadata epoch so warmed readers cannot retain old eligibility.
They do not change source-corpus revisions during the indexer's own probe publication.
Empty metadata-only hybrid requests use core results without model inference.
These explicit constraints do not establish abstention for free-form topic queries.
+40 -9
View File
@@ -33,6 +33,7 @@ struct FilterKey {
code_only: bool,
media_artist: Option<String>,
media_title: Option<String>,
metadata_revision: i64,
}
struct EligibilityCache {
@@ -186,16 +187,18 @@ impl Store {
}
}
initialization.execute_batch("CREATE TABLE IF NOT EXISTS semantic_media_errors(generation INTEGER NOT NULL REFERENCES semantic_generations(id) ON DELETE CASCADE,path TEXT NOT NULL,message TEXT NOT NULL,PRIMARY KEY(generation,path))")?;
// Metadata-only writes change strict eligibility even when file bytes do not change.
for (name, event, row) in [
("insert", "INSERT", "new"),
("update", "UPDATE", "new"),
("delete", "DELETE", "old"),
// Probe publication changes filter eligibility, not source discovery identity.
// Keep its epoch separate so indexing does not invalidate its own corpus snapshot.
initialization.execute_batch("CREATE TABLE IF NOT EXISTS semantic_media_metadata_revision(singleton INTEGER PRIMARY KEY CHECK(singleton=1), revision INTEGER NOT NULL);
INSERT OR IGNORE INTO semantic_media_metadata_revision(singleton,revision) VALUES(1,0);")?;
for (name, event) in [
("insert", "INSERT"),
("update", "UPDATE"),
("delete", "DELETE"),
] {
initialization.execute_batch(&format!("CREATE TRIGGER IF NOT EXISTS semantic_media_metadata_{name} AFTER {event} ON media_metadata BEGIN
UPDATE semantic_meta SET corpus_revision=corpus_revision+1 WHERE singleton=1;
INSERT INTO semantic_dirty_paths(path,revision) VALUES({row}.path,(SELECT corpus_revision FROM semantic_meta WHERE singleton=1))
ON CONFLICT(path) DO UPDATE SET revision=excluded.revision; END;"))?;
initialization.execute_batch(&format!("DROP TRIGGER IF EXISTS semantic_media_metadata_{name};
CREATE TRIGGER semantic_media_metadata_{name} AFTER {event} ON media_metadata BEGIN
UPDATE semantic_media_metadata_revision SET revision=revision+1 WHERE singleton=1; END;"))?;
}
incremental::migrate(&initialization)?;
initialization.commit()?;
@@ -646,6 +649,19 @@ impl Store {
.map(|ext| ext.trim_start_matches('.').to_ascii_lowercase());
let min_size = options.min_size.map(i64::try_from).transpose()?;
let max_size = options.max_size.map(i64::try_from).transpose()?;
let metadata_revision = if options.media_artist.is_some()
|| options.media_title.is_some()
|| options.min_duration_ms.is_some()
|| options.max_duration_ms.is_some()
{
snapshot.query_row(
"SELECT revision FROM semantic_media_metadata_revision WHERE singleton=1",
[],
|row| row.get(0),
)?
} else {
0
};
let filter = FilterKey {
folder,
extension,
@@ -667,6 +683,7 @@ impl Store {
.as_deref()
.map(searchi_core::normalize_media_field)
.transpose()?,
metadata_revision,
};
trace.end(filter_span, Default::default());
let eligibility_span = trace.begin("semantic.eligibility", parent);
@@ -1403,6 +1420,11 @@ mod tests {
.len(),
1
);
let corpus_before: i64 = store.connection.query_row(
"SELECT corpus_revision FROM semantic_meta WHERE singleton=1",
[],
|row| row.get(0),
)?;
tags.insert("artist".into(), "Replacement".into());
lexical.set_structured_media_metadata_at_revision(
&path,
@@ -1412,6 +1434,15 @@ mod tests {
identity.modified_ns,
revision,
)?;
let corpus_after: i64 = store.connection.query_row(
"SELECT corpus_revision FROM semantic_meta WHERE singleton=1",
[],
|row| row.get(0),
)?;
assert_eq!(
corpus_before, corpus_after,
"Probe publication must not invalidate source discovery"
);
assert!(store
.search(&vector, "metadata-filter-proof", 1, &options)?
.is_empty());
@@ -0,0 +1,140 @@
//! Explicit identities constrain genuine Google media retrieval, without a cosine cutoff.
use anyhow::{Context, Result};
use searchi_core::{IndexOptions, SearchIndex};
use searchi_service::{FfmpegTools, HybridSearch, MediaLimits, SearchOptions};
use std::{fs, path::Path, process::Command, sync::atomic::AtomicBool};
fn tagged_tone(ffmpeg: &Path, path: &Path, artist: &str, title: &str) -> Result<()> {
let output = Command::new(ffmpeg)
.args([
"-hide_banner",
"-loglevel",
"error",
"-nostdin",
"-threads",
"1",
"-f",
"lavfi",
"-i",
"sine=frequency=440:sample_rate=16000:duration=2",
"-c:a",
"pcm_s16le",
"-metadata",
])
.arg(format!("artist={artist}"))
.arg("-metadata")
.arg(format!("title={title}"))
.arg(path)
.output()?;
anyhow::ensure!(
output.status.success(),
"Owned FFmpeg fixture creation failed: {}",
String::from_utf8_lossy(&output.stderr)
);
Ok(())
}
#[test]
#[ignore = "Requires verified Google assets, normal native worker and FFmpeg helpers; CPU only"]
fn genuine_google_exact_music_constraints_survive_publication_and_hint_refresh() -> Result<()> {
assert_eq!(
std::env::var("SEARCHI_FORCE_CPU")?,
"1",
"VM inference must use CPU only"
);
let model =
std::env::var_os("SEARCHI_GEMMA_MODEL_DIR").context("Set SEARCHI_GEMMA_MODEL_DIR")?;
let worker = std::env::var_os("SEARCHI_GEMMA_WORKER").context("Set SEARCHI_GEMMA_WORKER")?;
let tools = FfmpegTools {
ffmpeg: std::env::var_os("SEARCHI_TEST_FFMPEG")
.context("Set SEARCHI_TEST_FFMPEG")?
.into(),
ffprobe: std::env::var_os("SEARCHI_TEST_FFPROBE")
.context("Set SEARCHI_TEST_FFPROBE")?
.into(),
};
let temp = tempfile::tempdir()?;
let root = temp.path().join("music");
fs::create_dir(&root)?;
let target = root.join("z-target.wav");
tagged_tone(&tools.ffmpeg, &target, "Björk", "Jóga")?;
// Identical audio is a deliberately strong distractor; identity must come from tags.
tagged_tone(
&tools.ffmpeg,
&root.join("a-distractor.wav"),
"Other Artist",
"Björk",
)?;
let db = temp.path().join("index.db");
let mut lexical = SearchIndex::open(&db)?;
lexical.index_folder(&root)?;
let mut indexer = HybridSearch::open(&db, &model, &worker, 1)?;
assert_eq!(
indexer.worker_info().context("Google worker info")?.device,
"cpu"
);
let report = indexer.index_embeddings_with_media(&tools, &MediaLimits::default())?;
assert!(report.failures.is_empty());
assert_eq!(report.media_files, 2);
assert_eq!(report.audio_inference_calls, 2);
drop(indexer);
let mut search = HybridSearch::open_query(&db, &model, &worker, 1)?;
let options = SearchOptions {
media_artist: Some("BJÖRK".into()),
media_title: Some("JÓGA".into()),
..Default::default()
};
let metadata = search.search_measured("", 1, &options, true)?;
assert_eq!(metadata.results.len(), 1);
assert_eq!(metadata.results[0].filename, "z-target.wav");
assert!(!metadata
.metrics
.trace
.context("debug trace")?
.iter()
.any(|span| span.name == "semantic.query_embedding"));
let query = "the sound of a sine wave tone";
let semantic = search.search_semantic(query, 1, &options)?;
assert_eq!(semantic.len(), 1);
assert_eq!(semantic[0].filename, "z-target.wav");
assert_eq!(
search.search(query, 1, &options)?[0].filename,
"z-target.wav"
);
let absent = SearchOptions {
media_artist: Some("Absent Artist".into()),
..Default::default()
};
assert!(search.search(query, 1, &absent)?.is_empty());
assert!(search.search("", 1, &absent)?.is_empty());
let collision = SearchOptions {
media_artist: Some("Jóga".into()),
..Default::default()
};
assert!(search.search(query, 1, &collision)?.is_empty());
// A native same-stat hint invalidates old tags and warmed ANN eligibility.
let canonical = target.canonicalize()?;
lexical.index_paths_cancellable(
&root,
std::slice::from_ref(&canonical),
&IndexOptions::default(),
&AtomicBool::new(false),
)?;
assert!(search.search("", 1, &options)?.is_empty());
assert!(search.search_semantic(query, 1, &options)?.is_empty());
let mut indexer = HybridSearch::open(&db, &model, &worker, 1)?;
let refreshed = indexer.index_embeddings_with_media(&tools, &MediaLimits::default())?;
assert!(refreshed.failures.is_empty());
drop(indexer);
assert_eq!(search.search("", 1, &options)?[0].filename, "z-target.wav");
assert_eq!(
search.search_semantic(query, 1, &options)?[0].filename,
"z-target.wav"
);
// Deletion is suppressed without retaining an exact identity match.
fs::remove_file(&target)?;
lexical.index_folder(&root)?;
assert!(search.search("", 1, &options)?.is_empty());
assert!(search.search_semantic(query, 1, &options)?.is_empty());
Ok(())
}
+56
View File
@@ -0,0 +1,56 @@
# Exact music identity filters
The current source supports exact `media_artist` and `media_title` constraints
in the desktop filter panel, CLI and read-only MCP. They compare genuine probed
full tag fields using Unicode lowercase and trimmed outer whitespace. Both
constraints combine with existing filters before lexical and ANN candidate
limits; comment text cannot impersonate an artist or title.
A metadata-only request can omit query text. It returns a bounded stable list
without query inference. Missing tags yield no match, including in semantic
search. This is a narrow, explicit constraint, not general natural-language
no-answer detection. Blank or over-1024-byte request fields fail explicitly;
oversized genuine tags remain keyword-searchable but have no exact identity key.
SQLite schema 14 preserves legacy keyword tags without guessing structured
identities from flattened text. Existing music needs a media indexing pass to
populate the new fields. A separate durable metadata epoch refreshes filtered eligibility without changing
the source-corpus snapshot during indexing. Metadata revision guards, same-stat event invalidation,
deletions and eligibility-cache invalidation retain source freshness.
CLI examples:
```sh
searchi search --db /path/to/index.db --media-artist "Nina Simone"
searchi search --db /path/to/index.db --media-title "Feeling Good"
searchi search --db /path/to/index.db "live recording" --media-artist "Nina Simone"
```
Replace `/path/to/index.db` with your existing media-indexed database. The desktop controls are **Artist (exact)** and
**Track title (exact)**. v0.1.4 downloads predate these additions.
Windows CPU checks: the full ordinary workspace suite passed (asset-dependent
checks remain explicitly ignored). Shared migration/lifecycle/eligibility tests, five CLI unit
tests including real MCP response, 48 native desktop tests and 61 frontend tests
passed. Strict workspace/CLI/native Clippy and formatting passed; frontend build
passed. Native visual interaction remains unexecuted. Actual Google CPU integration also passed in 58.59 seconds (whole test, not
search latency): two owned two-second 16 kHz mono PCM WAVs carried real
artist/title tags and identical audio. The initial generation made two actual
audio encodes. Query-role semantic and hybrid searches with limit one returned
the eligible file, absent artist/field-collision constraints returned empty,
metadata-only traces contained no query embedding, and same-stat invalidation,
actual media refresh and deletion remained correct with warmed readers.
The normal auto worker was forced to CPU with one thread. Its SHA-256 was
`c01cb3ec13cfca722ee768e8350e1399a8b9a4d14a24a35adc3473a5cfc82a73`.
The first integration run failed because metadata publication advanced the
source-corpus revision; the separate metadata epoch corrected this, and the
rerun passed. Service tests and strict workspace Clippy passed after the fix.
This tiny identity-contract test does not measure music understanding or general
no-answer quality.
Reproduce with verified assets/helpers and `SEARCHI_FORCE_CPU=1`:
```sh
cargo test -p searchi-service --test media_constraints_google_cpu --locked -- --ignored --nocapture --test-threads=1
```
+76
View File
@@ -0,0 +1,76 @@
# Music metadata hypothesis: actual Google CPU diagnostic
This known two-file diagnostic compares real audio evidence with real embedded
metadata. It does **not** change production ranking or establish independent
music quality, general abstention, or a higher media rating.
The preserved v1 run encoded eleven default audio windows and two whole-tag
passages. Adding the whole passage left music memories at **2/4 first hits**:
the violin's English similarity increased, but stayed below the wrong piano.
The subsequent v2 policy was frozen in source commit `7055004` before inference.
It encodes artist, title, album, genre and composer separately, in fixed order
with a shared 2048-byte UTF-8 budget and the neutral document title `File metadata`.
The file score is the maximum of its actual evidence, with no weights or cutoff.
This is a hypothesis motivated by v1, not untouched held-out testing.
## Recomputed results
All 65 original queries remain in the report. Three filter queries are explicitly
unsupported. Among 62 executed queries, only seven positive queries name one of
the two candidates; 50 positives name files outside this deliberately limited
pool and cannot receive a quality score. Five unfiltered no-answer queries remain.
| Two-file diagnostic | Audio only | Whole tags v1 | Individual fields v2 |
| --- | ---: | ---: | ---: |
| Music memories: correct first file | 2/4 | 2/4 | 4/4 |
| Music identity queries: correct first file | 2/3 | 2/3 | 3/3 |
| All eligible positives: correct first file | 4/7 | 4/7 | 7/7 |
| Eligible positives: recall within first five | 7/7 | 7/7 | 7/7 |
| Eligible positive MRR | 0.786 | 0.786 | 1.000 |
| No-answer queries returning candidates, failure | 5/5 | 5/5 | 5/5 |
The first-hit gains are `q049`, `q050` and `q056`, with no scored positive
regression. English violin selected the literal composer field
`Violinist WTD/LPC Jazz Band` (cosine 0.73714); German violin selected the actual
title `Jazz Violin Solo` (0.73508). Their wrong piano audio maxima were 0.68972
and 0.70654 respectively. The piano memories retained their original audio hits.
This improvement also strengthens unrelated nearest neighbors: the purple-elephant
violin negatives increased by 0.04994 and 0.04801 cosine, and the German quantum
entanglement negative by 0.00749. All negatives still return files. Consequently,
the diagnostic provides **no abstention improvement** and no safe rejection
threshold. Metadata is useful evidence, not a calibrated confidence score.
## Inputs, execution and limits
The actual piano tags identify Kimiko Ishizaka, Goldberg Variation 13, Classical
and J.S. Bach. The violin tags identify Violinist WTD, Jazz Violin Solo, Jazz and
the LPC Jazz Band. No instrument descriptions were generated from the waveform.
Blank/missing whitelisted fields add no vector; encoder, comments, URLs and
copyright are excluded. Misleading tags remain unverified literal evidence.
Metadata citations have a field and text, never an invented playback timestamp.
V2 used actual Google EmbeddingGemma 2 Q8 through the same native
`b11461-no-logits-v1` worker on CPU, one thread, 768 dimensions and a 2048-token
indexing context. Ten field encodes took **750.03 ms**, excluding model startup
and query inference. It made **zero audio inference calls**: per-query audio
maxima were reused verbatim from the SHA-verified v1 report. Independent read-only
recomputation checked query equality and exact retained audio/whole-tag rankings
for all 62 executed rows. This tiny shared-VM measurement is not a throughput
guarantee or a production ANN/hybrid measurement.
- Frozen v2 plan SHA-256:
`566bcd608ccf898abaceab32b247786e895368c8428b1bfa6e0bbb8396211b05`.
- Preserved v1 report SHA-256:
`f985ba639a14b8c0288b8fa7812d580f179f31c8cad0c21d67531c8a4fddc5b6`.
- Ignored v2 report `.searchi/qa/music-fields-v2-results.json`, 288944 bytes,
SHA-256 `c13ceca2150059a21e10ee190893a66f5ac82570675e7725e54524c660a2ff30`.
Reproduction commands and structural tests are in the
[evaluation tool README](../tools/media-eval/README.md). Before deployment,
evaluate independently frozen unseen music, missing/misleading metadata, additional
instrument/style hard negatives and the complete mixed candidate pool. Production
integration must retain real metadata field citations, source-byte/revision guards,
strict filters and generation policy invalidation. The broader
[media quality gates and experimental rating](MEDIA-QUALITY.md) remain unchanged.
+18 -2
View File
@@ -12,8 +12,24 @@ timestamps and irrelevant results when the requested content is absent. The
million-record lexical benchmark is deferred while this quality work proceeds.
Google EmbeddingGemma 2 remains the main text/image/audio/video search model.
- [ ] Evaluate actual whitelisted music metadata as additional Google text evidence, retaining raw audio and real metadata source citations.
- [ ] Evaluate finer local ASR token/word timing and source-preserving phrase windows with Google text retrieval; preserve every competitor and reject invalid timestamps.
- [x] Run frozen actual Google music metadata diagnostics: separate fields improved
known two-file music memories from 2/4 to 4/4; no-answer remained 5/5 failing.
This is hypothesis evidence, not independent production validation.
See [music evidence](MUSIC-METADATA.md).
- [ ] Validate and integrate metadata embeddings on independent unseen music and
the full mixed pool with real field citations and stale-source guards.
- [x] Implement exact artist/title constraints in shared storage, lexical/ANN
eligibility, CLI/MCP and desktop controls; metadata-only search skips inference.
Unit/lifecycle evidence is recorded separately from actual model validation.
- [x] Actual Google CPU constraint integration passed with genuine tagged WAVs,
semantic/hybrid limit-one retrieval, empty absent identities, no-inference
metadata lookup and warmed lifecycle refresh; see [evidence](MEDIA-CONSTRAINTS.md).
- [ ] Package and install an updated desktop release with these exact filters.
- [ ] Evaluate finer local ASR token/word timing and source-preserving phrase windows
with Google text retrieval. Ordinary-token run completed but failed interval
validation; DTW run stopped on invalid native UTF-8 and remains incomplete.
Neither receives an accepted score. Future diagnostics retain bounded failing
raw bytes and atomic checkpoints; see tools/speech-eval/README.md.
- [ ] Validate richer no-answer evidence on new independent positive/negative data; do not retune the failed universal cosine cutoff on old held-out results.
- [ ] Integrate accepted improvements into shared engine/UI/headless clients with verified setup, lifecycle and CPU performance evidence. Diagnostics alone do not raise the media quality rating.
- [x] Publish v0.1.0 Windows installer/checksum, verify public download bytes,