feat(audio): store, serve and match the v1 audio signature
Completes UR-009. The register recorded the signature as stored; it was not. `ingest` validated `cut.audio_signature` and wrote NULL, so every served manifest came back without one — which also meant the plugin's own alignment (jRay JR-047, Done) had nothing to align against and could never run. That is the failure mode a status field is least able to catch: every validation test passed and the feature delivered nothing. Now stored, coarse-indexed and served back byte-identically, with a held manifest adopting an incoming signature it lacked (§9a). `audio`-tier matching runs on every read endpoint via an `audio_signature` parameter, and `POST /manifests/search` answers the unknown-providence case with a runtime prefilter, a bounded scan and honest truncation reporting. Three rules §3 did not previously state, now normative: - **±1 frame of slack in the score.** scene-actor-extraction VR-014 measured the exact-frame rule demoting 27 of 40 correctly aligned releases to `loose`, because the two windows are cut on their own file's frame grid and those grids do not coincide. With ±1 frame all 40 reach `audio` (worst 0.906) and the strongest false match is unmoved at 0.16. - **The offset has two terms.** Both windows are anchored at their own file's runtime/2, so the slide alone is wrong by half the runtime difference on every shifted release. A signature without a runtime therefore cannot align, and is refused by name rather than answered at a lower tier. - **A signature verdict is final**, including its refusals. Falling back to the runtime tier after the audio declined would let a coincidence overturn direct evidence, inverting the ordering the tier table exists to state. The slide precomputes each frame's neighbourhood as a 32-bit bin set rather than re-deriving it across 1201 slides — 3.3 ms to 1.1 ms per candidate, with a test asserting exact equivalence to the rule written the obvious way. The 1000-candidate search cap follows from that measurement as a ~1.1 s ceiling per request, not a round number. jRay's matcher still implements the pre-slack rule and will label some alignments `loose` that this server calls `audio`. Nothing misaligns — JR-047 makes the local answer win — but that register now carries the follow-up. TRACES: UR-008, UR-009 | SR-003
This commit is contained in:
+25
-7
@@ -35,7 +35,7 @@ requirement and no fixture-generation step, unlike `scene-actor-extraction`.
|
||||
| UR-006 | Serve and accept a whole series in one operation | PR-006 | High | Done |
|
||||
| UR-007 | Plugin queries an ordered, configurable list of servers | PR-005 | High | In Progress |
|
||||
| UR-008 | Servers replicate manifests between each other | PR-006 | Medium | Done |
|
||||
| UR-009 | Store an audio spectral-peak signature for content-based identification | SR-003 | Medium | In Progress |
|
||||
| UR-009 | Store an audio spectral-peak signature for content-based identification | SR-003 | Medium | Done |
|
||||
| UR-010 | Identity crossing the API boundary is TMDB/IMDB ids, never a name alone | SR-001 | High | Done |
|
||||
| UR-011 | Reject any field capable of carrying binary or attacker-chosen content | SR-004 | High | Done |
|
||||
| UR-012 | Never accept, store, or serve gallery data — reference faces or embeddings | SR-005 | High | Done |
|
||||
@@ -68,11 +68,29 @@ a peer — is deliberately not an API: §9a requires that a peering exist only
|
||||
because an operator typed a URL, so it is a database action, and
|
||||
`there_is_no_endpoint_that_creates_a_peering` asserts the absence.
|
||||
|
||||
**UR-009 is `In Progress`.** The server accepts, validates and stores
|
||||
`cut.audio_signature`, and `content_id` correctly excludes it (§9a). What is
|
||||
absent is `audio`-tier matching and `POST /manifests/search`. This is the
|
||||
sequencing §3 recommends — accumulate signatures first, enable matching once
|
||||
coverage is useful — not an oversight.
|
||||
**UR-009 is `Done`.** The server accepts, validates, stores and serves
|
||||
`cut.audio_signature`; `content_id` correctly excludes it (§9a), and a held
|
||||
manifest lacking one adopts an incoming signature rather than discarding it.
|
||||
`audio`-tier matching runs on every read endpoint, and `POST /manifests/search`
|
||||
answers the unknown-providence case.
|
||||
|
||||
> **The register previously recorded this row as storing the signature, and it
|
||||
> did not.** `ingest` validated `cut.audio_signature` and then wrote `NULL`, so
|
||||
> every served manifest came back without one — which also meant the plugin's
|
||||
> own alignment (`jRay` JR-047, `Done`) had nothing to align against and could
|
||||
> never run. Recorded here because it is the failure mode a status field is
|
||||
> least able to catch: every validation test passed, and the feature delivered
|
||||
> nothing.
|
||||
|
||||
**§3's scoring rule changed with this row.** A frame now agrees within ±1
|
||||
frame rather than exactly, on `scene-actor-extraction` VR-014's measurement —
|
||||
the exact rule demoted 27 of 40 correctly aligned releases to `loose` because
|
||||
the two windows' frame grids do not coincide. **`jRay`'s `AudioSignatureMatcher`
|
||||
still implements the pre-change rule**, so the plugin will label some alignments
|
||||
`loose` that this server calls `audio`. Its local answer supersedes the server's
|
||||
on the fetch path (JR-047), so nothing is misaligned by the divergence — but the
|
||||
two are now out of step with §3, and the plugin register should carry the
|
||||
follow-up.
|
||||
|
||||
**UR-012 is satisfied structurally, by absence.** There is no field in the
|
||||
Jmanifest capable of carrying an embedding or a crop, and no endpoint that would
|
||||
@@ -144,7 +162,7 @@ topology is the point, so this is a deliberate choice rather than an oversight.
|
||||
| UR-006 | T2 | Bundle accepted per-episode, non-atomically | One bad episode rejected while its neighbours are accepted; envelope errors are whole-request `400` |
|
||||
| UR-007 | **external** | *No server-side test, and cannot have one.* The obligation is the plugin's: `jRay` JR-025, which is `Done` and tagged in that register | Verified there, not here — counted as covered by cross-reference, never by a test in this repo. **See the status note: JR-025 being `Done` does not by itself close UR-007**, because the fetch path (`jRay` JR-031) is still `Planned` |
|
||||
| UR-008 | T1 + T2 | Feed, fetch-by-hash, batch have, peer directory | **Cursor is strictly monotonic** — a ULID would sort out of write order within a millisecond and silently skip entries; a peer retraction flags rather than delists; only the opt-in abuse channel delists; `pending` is never replicated; **no endpoint can create a peering** |
|
||||
| UR-009 | T1 | Signature structurally validated | Fixed length; reserved high bit; **media < 120 s must send no signature at all** |
|
||||
| UR-009 | T1 + T2 | Signature validated, stored, served back, and matched on | Fixed length; reserved high bit; **media < 120 s must send no signature at all**; a `v2:` payload is refused rather than parsed; **the served signature is byte-identical to the contributed one** — a signature that is validated and then dropped passes every validation test and delivers nothing; a slide past the ±600-frame cap and unrelated content are both *declined*, never given a best-effort alignment; **the offset carries the window-anchor term**, not the slide alone; a signature disagreement is not overturned by a runtime coincidence; a search never returns a `pending` manifest |
|
||||
| UR-010 | T1 + T2 | Actors persist as TMDB person ids | A name the upload invented does not round-trip |
|
||||
| UR-011 | T2 | Every payload-shaped field rejected | base64, hex, markup, control characters, bidi overrides, compatibility homoglyphs |
|
||||
| UR-012 | T2 | No endpoint accepts embeddings or image data | An `embedding` or `crop` field is an unknown-field `400` |
|
||||
|
||||
Reference in New Issue
Block a user