diff --git a/.gitignore b/.gitignore index 0b72c24..e1ffc37 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,12 @@ obj/ .vs/ .idea/ artifacts + +# Python artefacts from the traceability tooling +__pycache__/ +*.pyc + +# Generated traceability output — regenerate with the gate, never hand-edit. +# `docs/traceability.md` is committed, as in the other two components; the JSON +# report is not, since nothing reads it back. +traces-report.json diff --git a/Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs b/Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs index 7edd668..7b5c368 100644 --- a/Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs +++ b/Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs @@ -279,6 +279,7 @@ public class ManifestExchangeClient : IManifestExchangeClient, IDisposable /// completion and then measuring is exactly the denial-of-service primitive /// the cap exists to prevent. /// + // TRACES: JR-028 | SR-004 private static async Task ReadCappedAsync( HttpResponseMessage response, long cap, diff --git a/SPEC.md b/SPEC.md index ef0647b..63223ed 100644 --- a/SPEC.md +++ b/SPEC.md @@ -55,9 +55,14 @@ The truth file is **not** the Jmanifest. It carries installation-local fields identity block the exchange adds. See §6. **Current:** [`Models/TruthFile.cs`](Jellyfin.Plugin.JRay/Models/TruthFile.cs), -`schema_version: 1`. **Gap:** the schema below is not implemented, and the other -two specs describe the pending bump in more detail than this one does — the -ownership is stated but not yet exercised. +`schema_version: 2` — the schema below is implemented, with +[`Models/TruthScene.cs`](Jellyfin.Plugin.JRay/Models/TruthScene.cs), +[`Models/TruthExtraction.cs`](Jellyfin.Plugin.JRay/Models/TruthExtraction.cs) and +[`Models/TruthCut.cs`](Jellyfin.Plugin.JRay/Models/TruthCut.cs). + +**Gap:** the ownership claim is still not *enforced* — nothing checks that the +other two repos reference this section rather than restating the schema. JR-001's +verification tier is `static` for that reason, and no such check exists yet. ### JR-002 — `schema_version: 2` @@ -118,9 +123,12 @@ same upload must agree on its `content_id`, and belief is a producer-side estimate that may legitimately differ between pipeline versions for identical timings. It replicates the way `audio_signature` does — see the server spec §9a. -**Gap:** entire requirement. The changes are all breaking and ship as **one** -bump (SR-003), together with extraction `IR-002` and the server's acceptance of -the new shape. +**Current:** implemented. `scenes` are `TruthScene` objects carrying `belief` and +`route`; `extraction.*` and `cut.*` are their own types; `anneal_sec` and the +top-level `sample_fps` are **deleted, not zeroed**. `ManifestConverter` carries +belief, route and both blocks through from a fetched manifest rather than +flattening them. **Gap:** none — this shipped with extraction `IR-002` and the +server's `jmanifest_version: 2` as the one coordinated SR-003 bump. ### JR-003 — Unknown `schema_version` is refused, never guessed @@ -140,9 +148,20 @@ version found, rather than silently reporting no coverage — an item that looks un-extracted when it was merely stale is the failure mode that wastes a user's compute. -**Current:** `PUT` rejects `schema_version != 1` with `400`; sidecar reads do not -check the version at all. **Gap:** the version check must move into the shared -read path so all three sources are covered, and the target becomes `2`. +**Current:** implemented. +[`Services/TruthSchema.cs`](Jellyfin.Plugin.JRay/Services/TruthSchema.cs) holds +the single `SupportedVersion = 2` and is consulted on **all four** paths — sidecar +read, managed store load, managed `PUT`, and the converter that writes a fetched +manifest. Rejections name the file and the version found, per the paragraph above. + +Managed truth is checked on load as well as on `PUT`, because data written by an +earlier plugin build is already on disk and never passed today's `PUT`. + +One consequence is worth stating: a v1 file usually fails to *deserialise* before +its version can be read, since `scenes` was a float pair and is now an object. So +both the parse-failure path and the version-gate path must name the file — a +rejection the user cannot attribute to a stale sidecar is the failure mode JR-003 +exists to prevent. **Gap:** none. ### JR-004 — A window is a scene-membership claim @@ -765,8 +784,42 @@ frames whose peak bin matches — is a **consumer** concern and belongs to this plugin. Offsets are applied client-side per JR-030; manifests are never rewritten. -**Current:** `ComputeAudioSignatures` exists as a configuration switch. **Gap:** -entire requirement, both computation and matching. +**Current:** JR-042 and JR-043 hold. `AudioSignature` implements the construction +above — band table, periodic Hann, radix-2 FFT, band-mean peak, energy class, +packing — and `AudioSignatureService` decodes the centre window by running the +FFmpeg binary `IMediaEncoder.EncoderPath` names, so the plugin gained no +dependency. `fixtures/audio/` holds the extraction repo's three golden-vector +files byte-identically, and the computed signature equals the recorded vector +exactly (UT-038 … UT-044). The binding check regenerates the fixture PCM from +`make_fixture.py`'s arithmetic and verifies it against the recorded decode +checksums, so it runs on a CI host with no codec at all; the two tests that +exercise the real FFmpeg decode self-skip without a binary. + +JR-044 and JR-045 hold as well, both halves. `AudioSignatureMatcher` is the +reader they were waiting on: it implements the ±600-frame slide above, scoring +the fraction of overlapping frames whose peak band agrees, and returns the tier +and the offset. `TryParseFrames` refuses any prefix but `v1:`, so a future +producer's `v2:` drops the item to the runtime tier rather than being scored as +if it were understood; and a runtime under 120 s yields no match and therefore no +offset, read off the runtime rather than inferred from a missing string, because +the runtime is what both producers test (UT-045 … UT-052). + +The offset has **two terms**, which is easy to miss: the recovered slide, and the +difference between where the two windows are anchored. Both windows are centred +on their own file's midpoint, so unequal runtimes start them at different +absolute times. A release carrying 40 s of extra head material recovers 20 s from +each term. + +One parameter is **not** from the specification: an alignment must overlap by at +least 64 frames (~6 s) before its score counts. Without a floor the extreme +offsets compare a handful of frames, where a chance agreement scores 1.0 and +beats the true alignment. It never binds on the real case — two full-length +signatures still overlap by 688 frames at the widest offset. + +**Gap:** the signature is computed and can now be read, but nothing yet *calls* +either: `ComputeAudioSignatures` still gates nothing, no fetch path attaches a +signature or consults the matcher, and no contribution carries one. That wiring +belongs to the fetch and contribute paths (JR-031, JR-034), not here. --- diff --git a/docs/requirements.md b/docs/requirements.md index a60c581..9483f44 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -48,10 +48,45 @@ Tag code with `// TRACES: JR-012 | SR-002`. | UT-026 | Provenance is **not** written into the truth file | JR-010, JR-004 | **Passing** | | UT-027 | `Delete` removes provenance too — no record outliving its claim | JR-010 | **Passing** | | UT-028 | Unknown item yields null rather than a fabricated record | JR-010 | **Passing** | +| UT-029 | A v2 file round-trips; `extraction.*` and `cut.*` survive intact — `sample_fps` read from the block, not the top level | JR-002 | **Passing** | +| UT-030 | `scenes` objects retain **belief and route** — a window that loses them is indistinguishable from v1 | JR-002 | **Passing** | +| UT-031 | All three routes (`live`, `deferred`, `pooled`) survive a round trip | JR-002 | **Passing** | +| UT-032 | A window without belief reads `null`, **not `0.0`** — absent and disbelieved are different claims | JR-002 | **Passing** | +| UT-033 | `schema_version` 1, 3 and 0 are all **refused**, and the message names the version found | JR-003 | **Passing** | +| UT-034 | A **missing** `schema_version` is refused, never assumed current | JR-003 | **Passing** | +| UT-035 | A `null` truth file is refused without throwing | JR-003 | **Passing** | +| UT-036 | **A v1 file never half-parses into usable windows** — it either fails to deserialise or is stopped by the gate | JR-003 | **Passing** | +| UT-037 | **The producer's actual output parses** — the exact shape `result_sink_node.hpp` writes, omitting `cut` and two `extraction` fields, not the spec's fully populated example | JR-002 | **Passing** | +| UT-038 | The **regenerated** fixture PCM matches the recorded `s16le` and `f32le` checksums — the input is proven identical before any DSP claim is made | JR-043 | **Passing** | +| UT-039 | **Signature equals the shared golden vector, byte for byte** — the same string the C++ producer emits | JR-042, JR-043 | **Passing** | +| UT-040 | Band → FFT-bin table matches the recorded one, and tiles 300–3000 Hz contiguously with no empty band | JR-042 | **Passing** | +| UT-041 | Well-formed: `v1:` prefix, 1288 frames, bit 7 always clear, **and the fixture still exercises all 32 bands and all 4 energy classes** | JR-042 | **Passing** | +| UT-042 | Whole frames only — 4095 samples yield nothing, 5120 yield two; a partial frame is never padded into a signature | JR-042 | **Passing** | +| UT-043 | The **real FFmpeg decode** of the fixture reproduces the golden signature | JR-042 | **Passing** (needs a binary) | +| UT-044 | **The window is taken from the centre**: the fixture wrapped in 90 s of silence either side signs identically | JR-042 | **Passing** (needs a binary) | +| UT-045 | **The 120 s boundary, on one file**: runtime 119.999 emits nothing, runtime 120.000 emits the golden signature — only the runtime differs, so a null cannot be blamed on the decode | JR-044 | **Passing** (boundary needs a binary) | +| UT-046 | Two identical, perfectly valid signatures still yield **no match and no offset** when either runtime is under the window — the rule is read off the runtime, not inferred from a missing string | JR-044 | **Passing** | +| UT-047 | A `v2:` signature whose payload is byte-identical to a valid v1 one is **refused, not parsed** — by the matcher as well as the parser | JR-045 | **Passing** | +| UT-048 | A v1 signature parses to exactly the produced frames, checked against the **golden vector** rather than against the producer's own output | JR-045 | **Passing** | +| UT-049 | Missing prefix, empty payload, invalid base64 and a **set reserved bit** are each refused without throwing — the client never accepts what the server would reject | JR-045 | **Passing** | +| UT-050 | Identical signatures score 1.0 at offset 0 and reach the `audio` tier | JR-044 | **Passing** | +| UT-051 | **A shifted release recovers its offset** rather than failing to match — the case the feature exists for | JR-044 | **Passing** | +| UT-052 | Unrelated content yields **no match at all**, and runtime skew contributes its window-anchor term to the offset | JR-044 | **Passing** | -All execute and pass. The suite is also checked to **fail** on deliberate -mutations, because a suite that has only ever passed is not evidence that it -tests anything. Three so far, each restored and re-verified afterwards: +All execute and pass. UT-043 and UT-044 are the two that need an FFmpeg binary, +which the plugin gets from Jellyfin at run time and a bare CI container may not +have; they self-skip without one. That is why the cross-repo claim rests on +UT-038 and UT-039, which regenerate the fixture PCM from `make_fixture.py`'s +arithmetic and need no codec at all — a check that skips is not a check. + +UT-045 follows the same rule: its *below-the-boundary* half is codec-free and +always binds, because the runtime check short-circuits before the encoder is +consulted — asserted with a deliberately invalid encoder path, so passing proves +the short-circuit rather than merely a failed decode. + +The suite is also checked to **fail** on deliberate mutations, because a suite +that has only ever passed is not evidence that it tests anything. Each was +restored and re-verified afterwards: | Mutation | Fails | Blast radius | |---|---|---| @@ -59,10 +94,23 @@ tests anything. Three so far, each restored and re-verified afterwards: | Downgrade the missing-dependency warning to `Information` | UT-013 | 1 test | | Make the window end bound exclusive (`t < end`) | UT-016, UT-018 | 2 tests | | Stop `Delete` removing provenance | UT-027 | 1 test | +| Make `TruthSchema.IsSupported` accept any version | UT-033, UT-034, UT-035, UT-036 | 4 tests | +| Emit `v2:` as the signature prefix | UT-039, UT-043, UT-044 | 3 tests | +| Aggregate a band by **sum** instead of mean | UT-039, UT-043, UT-044 | 3 tests | +| Anchor the decode window at the head instead of the centre | UT-044 | 1 test | -The third is the one worth keeping: a single character turns an inclusive window -into a half-open one, which would drop an actor at exactly the moment a scene -ends — and nothing else in the suite would have noticed. +Two further mutations were tried and **did not fail**, which is worth recording +rather than hiding: the symmetric `N-1` Hann window in place of the periodic one, +and the lower median in place of the upper as the energy reference. Both are +pinned by prose in the shared fixture, and on this synthetic vector neither moves +a peak bin or crosses an energy-class edge. They are conventions the golden +vector does not police, so a second implementation could get either wrong and +still pass — the fixture would need frames sitting nearer those boundaries to +catch it. + +The window-bound mutation is the one worth keeping: a single character turns an +inclusive window into a half-open one, which would drop an actor at exactly the +moment a scene ends — and nothing else in the suite would have noticed. `JR` is flat rather than split by theme. The plugin is one deployable with one audience, and the thematic grouping lives in the section headings below, where it @@ -81,8 +129,8 @@ coordinated `schema_version` bumps (SR-003). | ID | Requirement | Traces to | Priority | Status | |---|---|---|---|---| | JR-001 | The truth-file format is normatively defined here; other repos reference it rather than restating it | SR-003 | High | In Progress | -| JR-002 | `schema_version: 2` shape — `extraction.*` provenance block, `cut.*` block, `scenes` as objects carrying belief and route | SR-003 | High | Planned | -| JR-003 | Reject an unknown `schema_version`, never guess. **Flag day: v2 only**, no dual-accept | SR-003 | High | Planned | +| JR-002 | `schema_version: 2` shape — `extraction.*` provenance block, `cut.*` block, `scenes` as objects carrying belief and route | SR-003 | High | **Done** (UT-029…032) — `TruthScene`, `TruthExtraction`, `TruthCut`; `anneal_sec` and top-level `sample_fps` deleted, not zeroed. `ManifestConverter` carries belief, route and both blocks through | +| JR-003 | Reject an unknown `schema_version`, never guess. **Flag day: v2 only**, no dual-accept | SR-003 | High | **Done** (UT-033…036) — `TruthSchema.IsSupported` is the single gate, applied on all four paths: sidecar read, managed store load, managed `PUT`, converted manifest. Rejections name the file and the version found | | JR-004 | A window is a **scene-membership claim**, not a recognition event — never reinterpreted, merged, split or trimmed | **SR-002** | High | **Done** (UT-021, UT-022) | | JR-005 | Query semantics: actor present at `t` if any window contains `t`; presentation must not assert instantaneous visibility | **SR-002** | High | **Done** (UT-016…019) | | JR-006 | Read path holds up under **numerous** windows — no assumption of a handful of long ones | SR-002 | Medium | **Done** (UT-020, UT-023) | @@ -176,10 +224,10 @@ than assumed to be inherited. | ID | Requirement | Traces to | Priority | Status | |---|---|---|---|---| -| JR-042 | Compute the signature **exactly** per server spec §3, using the FFmpeg binary Jellyfin already ships via `IMediaEncoder.EncoderPath` — no new dependency | SR-003 | Medium | Planned | -| JR-043 | Golden-vector fixture **shared with the extraction repo**, proving the two implementations are bit-exact | SR-003 | High | Planned | -| JR-044 | Media shorter than 120 s: emit no signature and apply no sync offset — identical rule in both producers | SR-003 | Low | Planned | -| JR-045 | Emit and honour the signature's own `v1:` prefix, so a DSP change is detectable rather than silently non-matching | SR-003 | Low | Planned | +| JR-042 | Compute the signature **exactly** per server spec §3, using the FFmpeg binary Jellyfin already ships via `IMediaEncoder.EncoderPath` — no new dependency | SR-003 | Medium | **Done** (UT-039…044) — `AudioSignature` is the DSP, `AudioSignatureService` the decode; FFmpeg is invoked as a child process for decode, downmix and resample, and nothing was added to the project's dependencies | +| JR-043 | Golden-vector fixture **shared with the extraction repo**, proving the two implementations are bit-exact | SR-003 | High | **Done** (UT-038, UT-039) — `fixtures/audio/` holds the extraction repo's three files byte-identically; the C# signature equals the recorded vector exactly | +| JR-044 | Media shorter than 120 s: emit no signature and apply no sync offset — identical rule in both producers | SR-003 | Low | **Done** (UT-045, UT-046, UT-050…052) — the producer half was already in `AudioSignatureService`; the consumer half needed a reader, so `AudioSignatureMatcher` implements the §3 slide and declines an offset outright below the window. The boundary is asserted on one file at 119.999 s and 120.000 s | +| JR-045 | Emit and honour the signature's own `v1:` prefix, so a DSP change is detectable rather than silently non-matching | SR-003 | Low | **Done** (UT-047…049) — `AudioSignatureMatcher.TryParseFrames` refuses any prefix but `v1:`, and refuses malformed or structurally invalid payloads, so a future producer's `v2:` drops the item to the runtime tier instead of scoring as if it were understood | ## Human-in-the-loop association (JR-046) @@ -263,8 +311,8 @@ framework reference and leans on `RollForward` to reach the 10.0 runtime. | ID | Tier | Test asserts | Edge cases to cover | |---|---|---|---| | JR-001 | static | Other repos' specs link here rather than restating the schema | A second copy of the schema anywhere is the failure | -| JR-002 | T1 | A v2 file round-trips; `scenes` objects retain belief and route | Window with belief exactly at the ownership threshold; all three route values | -| JR-003 | **T1** | `schema_version` 1 and 3 are both **rejected**, not coerced | Missing field entirely; non-integer value | +| JR-002 | T1 | A v2 file round-trips; `scenes` objects retain belief and route | All three route values; belief absent reads `null` not `0.0`; **the producer's real output**, which omits `cut` and two `extraction` fields — a test written only against the spec's populated example would have passed throughout the break | +| JR-003 | **T1** | `schema_version` 1 and 3 are both **rejected**, not coerced | Missing field entirely; `null` document; **a v1 file must not half-parse into usable windows** | | JR-004 | T1 | Windows are stored and served byte-identical to input | Adjacent windows that "look" mergeable must **not** merge | | JR-005 | T1 | `t` exactly on `start` and on `end` are both present | Zero-length window; overlapping windows for one actor | | JR-006 | T1 | Response bounded by actor count, not window count; lookup not quadratic | 50 × 1000 windows; **unsorted input still resolves** — sortedness is a producer guarantee, never a correctness dependency | @@ -303,8 +351,8 @@ framework reference and leans on `RollForward` to reach the 10.0 runtime. | JR-039 | T1 | Batch never exceeds 100 items | Library of 10⁴ items produces a paced sweep | | JR-040 | **T4** | Config page states the per-server exposure | Manual review of copy | | JR-041 | **static** | No embedding or image field is parsed or stored | Grep-based, mirroring the server's UR-012 | -| JR-042 | T1 | DSP chain matches the specified parameters exactly | Window, hop, band, bin count each asserted individually | -| JR-043 | **T1** | Signature matches the shared golden vector **bit-for-bit** | Media < 120 s → no signature; identical result in both repos | +| JR-042 | T1 | DSP chain matches the specified parameters exactly | Window, hop, band, bin count each asserted individually; the decode itself is covered only where an FFmpeg binary exists, so it must not be the only cover for any claim | +| JR-043 | **T1** | Signature matches the shared golden vector **bit-for-bit** | The fixture PCM is regenerated from `make_fixture.py` and checked against the recorded decode checksums first, so the check binds on a host with no codec and a decode divergence is distinguishable from a DSP one | | JR-044 | T1 | Media < 120 s yields no signature and no offset | Exactly 120 s — the boundary both repos must agree on | | JR-045 | T1 | `v1:` emitted; an unknown prefix is refused, not parsed | `v2:` signature from a future producer | | JR-046 | T2 + **T4** | *Assertions deferred* — recording an association and persisting it is T2; the review UI itself is T4 | Cannot be written until the truth-file interface for unidentified presence is settled (system open question 2) and AR-021/AR-022 land | diff --git a/docs/traceability.md b/docs/traceability.md new file mode 100644 index 0000000..ed3000c --- /dev/null +++ b/docs/traceability.md @@ -0,0 +1,785 @@ +# Requirements traceability matrix + + + + +**Generated:** 2026-07-31T14:19:46+00:00 + +Denominators are read from [`requirements.md`](requirements.md) at run time, never hardcoded. Coverage counts a requirement only when it is tagged in source **and** has a verification tier this repo's CI host can execute (`T1, T2, static`). + +## Summary + +| Metric | Value | +|---|---| +| Source files scanned | 74 | +| TRACES tags found | 45 | +| EXCEPTION tags found | 0 | +| Requirements defined | 46 | +| Requirements covered | 37 | +| **Coverage** | **80.4%** (37/46) | +| Coverage of CI-executable scope | 84.1% (37/44) | +| Tagged but unexecuted in CI | 1 | +| Orphan tags | 0 | + +### By type + +| Type | Covered | Tagged but unexecuted | Defined | +|---|---|---|---| +| JR | 37 | 1 | 46 | + +- **UT** tags present (separate taxonomy, not counted in coverage): UT-001, UT-002, UT-003, UT-004, UT-005, UT-007, UT-008, UT-009, UT-010, UT-011, UT-012, UT-013, UT-014, UT-015, UT-016, UT-017, UT-018, UT-019, UT-020, UT-021, UT-022, UT-023, UT-024, UT-025, UT-026, UT-027, UT-028, UT-029, UT-030, UT-031, UT-032, UT-033, UT-034, UT-035, UT-036, UT-037, UT-038, UT-039, UT-040, UT-041, UT-042, UT-043, UT-044, UT-045, UT-046, UT-047, UT-048, UT-049, UT-050, UT-051, UT-052 +- **PR** tags present (separate taxonomy, not counted in coverage): PR-001, PR-003, PR-004, PR-005, PR-006 +- **SR** tags present (separate taxonomy, not counted in coverage): SR-001, SR-002, SR-003, SR-004, SR-005 + +## Not executable in CI + +These requirements have no verification tier this repo's CI host can run, so a tag on them is evidence of *intent*, not of verification. They are never counted as covered. + +| ID | Tiers | Tagged in source | Requirement | +|---|---|---|---| +| JR-020 | T4 | yes | Pause overlay: injected client script queries `jray?t=` and renders t… | +| JR-040 | T4 | no | The config page states plainly that **each configured server multipli… | + +**Tagged but unexecuted:** JR-020 — a test exists and is tagged, but this CI host cannot run it. Report those runs separately. + +## Orphan tags + +A tag naming an ID `requirements.md` does not define. This is what renumbering produces, and what a typo produces. + +_None._ + +## Requirements tracing up to nothing + +A register row whose `Traces to` cell names no parent. Work serving no stated goal is how scope creeps in, and it is invisible unless something looks. + +_None._ + +## Recorded exceptions + +Deliberate, documented departures from an invariant (`EXCEPTION: XX-nnn `). Reported separately and never counted as coverage — an exception is a decision to be reviewed, not evidence a requirement is met. + +_None._ + +## Register + +| ID | Status | Tier | Traces to | Trace state | Tagged in | Requirement | +|---|---|---|---|---|---|---| +| JR-001 | In Progress | static | SR-003 | covered | `Jellyfin.Plugin.JRay/Models/TruthFile.cs` | The truth-file format is normatively defined here; other repos refere… | +| JR-002 | **Done** (UT-029…03… | T1 | SR-003 | covered | `Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs`, `Jellyfin.Plugin.JRay/Models/TruthCut.cs`, `Jellyfin.Plugin.JRay/Models/TruthExtraction.cs`, `Jellyfin.Plugin.JRay/Models/TruthFile.cs`, `Jellyfin.Plugin.JRay/Models/TruthScene.cs` | `schema_version: 2` shape — `extraction.*` provenance block, `cut.*` … | +| JR-003 | **Done** (UT-033…03… | T1 | SR-003 | covered | `Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs`, `Jellyfin.Plugin.JRay/Controllers/TruthController.cs`, `Jellyfin.Plugin.JRay/Services/TruthSchema.cs` | Reject an unknown `schema_version`, never guess. **Flag day: v2 only*… | +| JR-004 | **Done** (UT-021, U… | T1 | **SR-002** | covered | `Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs`, `Jellyfin.Plugin.JRay/Controllers/ActorsController.cs`, `Jellyfin.Plugin.JRay/Models/TruthActor.cs`, `Jellyfin.Plugin.JRay/Models/TruthScene.cs`, `Jellyfin.Plugin.JRay/Services/PresenceLookup.cs` | A window is a **scene-membership claim**, not a recognition event — n… | +| JR-005 | **Done** (UT-016…01… | T1 | **SR-002** | covered | `Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs`, `Jellyfin.Plugin.JRay/Controllers/ActorsController.cs`, `Jellyfin.Plugin.JRay/Models/ActorInScene.cs`, `Jellyfin.Plugin.JRay/Services/PresenceLookup.cs`, `Jellyfin.Plugin.JRay/Web/jray-overlay.js` | Query semantics: actor present at `t` if any window contains `t`; pre… | +| JR-006 | **Done** (UT-020, U… | T1 | SR-002 | covered | `Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs`, `Jellyfin.Plugin.JRay/Services/PresenceLookup.cs` | Read path holds up under **numerous** windows — no assumption of a ha… | +| JR-007 | Done | T1 | SR-001 | covered | `Jellyfin.Plugin.JRay/Models/TruthActor.cs` | Identity is public identifiers: prefer `jellyfin_id` locally, else re… | +| JR-008 | Done | T1 | PR-001 | covered | `Jellyfin.Plugin.JRay/Services/TruthDataService.cs` | Discover a sidecar truth file beside the media, by configurable suffix | +| JR-009 | Done | T2 | PR-004 | covered | `Jellyfin.Plugin.JRay/Controllers/TruthController.cs`, `Jellyfin.Plugin.JRay/Services/ManagedTruthStore.cs` | Accept truth data pushed by a remote worker (`PUT`/`DELETE`), admin k… | +| JR-010 | **Done** (UT-024…02… | T1 | PR-001 | covered | `Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs`, `Jellyfin.Plugin.JRay/Controllers/ActorsController.cs`, `Jellyfin.Plugin.JRay/Models/TruthProvenance.cs`, `Jellyfin.Plugin.JRay/Services/ManagedTruthStore.cs`, `Jellyfin.Plugin.JRay/Services/TruthDataService.cs` | Precedence: managed truth (pushed **or** fetched) overrides a sidecar… | +| JR-011 | Done | T1 | PR-001 | covered | `Jellyfin.Plugin.JRay/Services/TruthDataService.cs` | Loaded truth is cached; any write invalidates the item's cache entry … | +| JR-012 | Done | T2 | PR-001 | covered | `Jellyfin.Plugin.JRay/Controllers/ActorsController.cs` | `GET .../Timeline` returns the full truth file for an item | +| JR-013 | Done | T2 | PR-001 | covered | `Jellyfin.Plugin.JRay/Controllers/ActorsController.cs` | `GET .../jray?t=` returns an **extensible** context envelope; consume… | +| JR-014 | Done | T2 | PR-004 | covered | `Jellyfin.Plugin.JRay/Controllers/ActorsController.cs`, `Jellyfin.Plugin.JRay/Controllers/PolicyController.cs`, `Jellyfin.Plugin.JRay/Controllers/TruthController.cs`, `Jellyfin.Plugin.JRay/Controllers/WebController.cs` | Authorisation: reads need an authenticated user, admin routes need th… | +| JR-015 | Done | T2 | PR-003 | covered | `Jellyfin.Plugin.JRay/Controllers/TasksController.cs` | `Tasks/Pending` serves a random sample of items with no truth data, s… | +| JR-016 | **Done** (UT-007…01… | T1 | PR-003 | covered | `Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs`, `Jellyfin.Plugin.JRay/Controllers/PolicyController.cs`, `Jellyfin.Plugin.JRay/Services/MediaPolicyStore.cs`, `Jellyfin.Plugin.JRay/Services/PolicyResolver.cs` | Prioritise/ignore rules scoped `Genre` / `Series` / `Item`; **most sp… | +| JR-017 | Done | T1 | PR-003 | covered | `Jellyfin.Plugin.JRay/Controllers/TasksController.cs` | Rules steer **work discovery only** — never the overlay or the read e… | +| JR-018 | Done | T1 | PR-003 | covered | `Jellyfin.Plugin.JRay/Controllers/CoverageController.cs` | Coverage report by media type and genre; ignored items leave the perc… | +| JR-019 | Done | T2 | PR-003 | covered | `Jellyfin.Plugin.JRay/Controllers/CoverageController.cs` | Picker endpoints (genres, series, item search) populate the rule edit… | +| JR-020 | Done | T4 | **PR-001** | tagged, unexecuted | `Jellyfin.Plugin.JRay/Controllers/WebController.cs`, `Jellyfin.Plugin.JRay/Services/FileTransformationRegistration.cs`, `Jellyfin.Plugin.JRay/Web/jray-overlay.js` | Pause overlay: injected client script queries `jray?t=` and renders t… | +| JR-021 | **Done** | static | PR-004 | covered | `Jellyfin.Plugin.JRay/Services/WebClientPatchService.cs`, `scripts/checks/no-index-injection.sh` | **jRay never injects into `index.html` on disk.** File Transformation… | +| JR-022 | **Done** (UT-001…00… | T1 | PR-004 | covered | `Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs`, `Jellyfin.Plugin.JRay/Services/WebClientPatchService.cs` | Migration: remove any on-disk patch left by an earlier jRay, identifi… | +| JR-023 | **Done** (UT-012…01… | T1, T4 | PR-004 | covered | `Jellyfin.Plugin.JRay.Tests/FileTransformationRegistrationTests.cs`, `Jellyfin.Plugin.JRay/Controllers/StatusController.cs`, `Jellyfin.Plugin.JRay/Services/FileTransformationRegistration.cs` | Absent the dependency, disable **only** the overlay and say so in the… | +| JR-024 | Done | T1 | SR-004 | covered | `Jellyfin.Plugin.JRay/Web/jray-overlay.js` | Actor names and all server-supplied strings render as **text, never m… | +| JR-025 | Done | T1 | PR-006 | covered | `Jellyfin.Plugin.JRay/Configuration/ManifestServer.cs`, `Jellyfin.Plugin.JRay/Controllers/ManifestController.cs`, `Jellyfin.Plugin.JRay/Models/Jmanifest.cs`, `Jellyfin.Plugin.JRay/Services/Interfaces/IManifestExchangeClient.cs`, `Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs` | Query an **ordered list** of servers; first result clearing the confi… | +| JR-026 | Planned | T1 | PR-006 | untagged | - | For a series, first-match applies per **episode** — later servers are… | +| JR-027 | Done | T1 | SR-004 | covered | `Jellyfin.Plugin.JRay/Models/Jmanifest.cs`, `Jellyfin.Plugin.JRay/Services/ManifestValidator.cs` | Treat **every** server as untrusted, including the default: re-valida… | +| JR-028 | Done | T1 | SR-004 | covered | `Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs` | Enforce response size caps **while streaming** — 2 MiB single, 25 MiB… | +| JR-029 | Done | T1 | SR-004 | covered | `Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs` | HTTPS required for non-loopback servers; certificate validation must … | +| JR-030 | Done | T1 | SR-003 | covered | `Jellyfin.Plugin.JRay/Services/ManifestConverter.cs`, `Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs` | Apply an `audio`-tier `offset` to **every** window before storing — s… | +| JR-031 | Planned | T2 | PR-006 | covered | `Jellyfin.Plugin.JRay/Controllers/ManifestController.cs` | Fetch endpoints: item fetch, series bundle fetch, per-server status, … | +| JR-032 | Planned | T1 | PR-006 | untagged | - | Identify is **never automatic** — storing a candidate is a separate c… | +| JR-033 | Planned | T1 | PR-006 | untagged | - | Scheduled sweep over items lacking truth data, using the **batch** `e… | +| JR-034 | Planned | T1 | PR-005 | untagged | - | Contribution strips `movie` and `jellyfin_id`, attaches identity from… | +| JR-035 | Planned | T1 | PR-006 | untagged | - | Uploads set `Expect: 100-continue`, so a rejection lands before a bun… | +| JR-036 | In Progress | T1 | PR-006 | covered | `Jellyfin.Plugin.JRay/Configuration/PluginConfiguration.cs`, `Jellyfin.Plugin.JRay/Models/TruthProvenance.cs` | Minimum accepted match tier is configurable; a `loose` match surfaces… | +| JR-037 | Done | T1 | PR-006 | covered | `Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs` | A server that is unreachable or failing is skipped on a short timeout… | +| JR-038 | Done | T1 | **PR-005** | covered | `Jellyfin.Plugin.JRay/Configuration/PluginConfiguration.cs` | Every exchange feature is **opt-in and off by default**, including th… | +| JR-039 | Planned | T1 | **PR-005** | untagged | - | No library-wide inventory in one request: batch `exists` capped at 10… | +| JR-040 | Planned | T4 | **PR-005** | untagged | - | The config page states plainly that **each configured server multipli… | +| JR-041 | Done | static | **SR-005** | covered | `scripts/checks/no-gallery-data.sh` | The plugin never fetches, stores, or transmits gallery data — referen… | +| JR-042 | **Done** (UT-039…04… | T1 | SR-003 | covered | `Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs`, `Jellyfin.Plugin.JRay/Services/AudioSignature.cs`, `Jellyfin.Plugin.JRay/Services/AudioSignatureService.cs` | Compute the signature **exactly** per server spec §3, using the FFmpe… | +| JR-043 | **Done** (UT-038, U… | T1 | SR-003 | covered | `Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs` | Golden-vector fixture **shared with the extraction repo**, proving th… | +| JR-044 | **Done** (UT-045, U… | T1 | SR-003 | covered | `Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs`, `Jellyfin.Plugin.JRay/Services/AudioSignatureMatcher.cs` | Media shorter than 120 s: emit no signature and apply no sync offset … | +| JR-045 | **Done** (UT-047…04… | T1 | SR-003 | covered | `Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs`, `Jellyfin.Plugin.JRay/Services/AudioSignatureMatcher.cs` | Emit and honour the signature's own `v1:` prefix, so a DSP change is … | +| JR-046 | **TBD** | T2, T4 | [system §4](../scripts/vendor/jray-proj… | untagged | - | Review UI for unidentified track clusters: show context crops, pick f… | + +## Detailed mapping + +### JR-001 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Models/TruthFile.cs:26`](../Jellyfin.Plugin.JRay/Models/TruthFile.cs#L26) — `public class TruthFile` + +### JR-002 + +**Locations:** 5 + +- [`Jellyfin.Plugin.JRay/Models/TruthCut.cs:15`](../Jellyfin.Plugin.JRay/Models/TruthCut.cs#L15) — `public class TruthCut` +- [`Jellyfin.Plugin.JRay/Models/TruthExtraction.cs:21`](../Jellyfin.Plugin.JRay/Models/TruthExtraction.cs#L21) — `public class TruthExtraction` +- [`Jellyfin.Plugin.JRay/Models/TruthFile.cs:26`](../Jellyfin.Plugin.JRay/Models/TruthFile.cs#L26) — `public class TruthFile` +- [`Jellyfin.Plugin.JRay/Models/TruthScene.cs:21`](../Jellyfin.Plugin.JRay/Models/TruthScene.cs#L21) — `public class TruthScene` +- [`Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs#L16) — `public class TruthSchemaTests` + +### JR-003 + +**Locations:** 3 + +- [`Jellyfin.Plugin.JRay/Controllers/TruthController.cs:28`](../Jellyfin.Plugin.JRay/Controllers/TruthController.cs#L28) — `public class TruthController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Services/TruthSchema.cs:35`](../Jellyfin.Plugin.JRay/Services/TruthSchema.cs#L35) — `public static class TruthSchema` +- [`Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs#L16) — `public class TruthSchemaTests` + +### JR-004 + +**Locations:** 5 + +- [`Jellyfin.Plugin.JRay/Controllers/ActorsController.cs:26`](../Jellyfin.Plugin.JRay/Controllers/ActorsController.cs#L26) — `public class ActorsController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Models/TruthActor.cs:20`](../Jellyfin.Plugin.JRay/Models/TruthActor.cs#L20) — `public class TruthActor` +- [`Jellyfin.Plugin.JRay/Models/TruthScene.cs:21`](../Jellyfin.Plugin.JRay/Models/TruthScene.cs#L21) — `public class TruthScene` +- [`Jellyfin.Plugin.JRay/Services/PresenceLookup.cs:27`](../Jellyfin.Plugin.JRay/Services/PresenceLookup.cs#L27) — `public static class PresenceLookup` +- [`Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs:19`](../Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs#L19) — `public class PresenceLookupTests` + +### JR-005 + +**Locations:** 5 + +- [`Jellyfin.Plugin.JRay/Controllers/ActorsController.cs:26`](../Jellyfin.Plugin.JRay/Controllers/ActorsController.cs#L26) — `public class ActorsController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Models/ActorInScene.cs:15`](../Jellyfin.Plugin.JRay/Models/ActorInScene.cs#L15) — `public class ActorInScene` +- [`Jellyfin.Plugin.JRay/Services/PresenceLookup.cs:27`](../Jellyfin.Plugin.JRay/Services/PresenceLookup.cs#L27) — `public static class PresenceLookup` +- [`Jellyfin.Plugin.JRay/Web/jray-overlay.js:14`](../Jellyfin.Plugin.JRay/Web/jray-overlay.js#L14) — `function truncate(text, maxLength)` +- [`Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs:19`](../Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs#L19) — `public class PresenceLookupTests` + +### JR-006 + +**Locations:** 2 + +- [`Jellyfin.Plugin.JRay/Services/PresenceLookup.cs:27`](../Jellyfin.Plugin.JRay/Services/PresenceLookup.cs#L27) — `public static class PresenceLookup` +- [`Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs:19`](../Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs#L19) — `public class PresenceLookupTests` + +### JR-007 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Models/TruthActor.cs:20`](../Jellyfin.Plugin.JRay/Models/TruthActor.cs#L20) — `public class TruthActor` + +### JR-008 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Services/TruthDataService.cs:25`](../Jellyfin.Plugin.JRay/Services/TruthDataService.cs#L25) — `public sealed class TruthDataService : ITruthDataService` + +### JR-009 + +**Locations:** 2 + +- [`Jellyfin.Plugin.JRay/Controllers/TruthController.cs:28`](../Jellyfin.Plugin.JRay/Controllers/TruthController.cs#L28) — `public class TruthController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Services/ManagedTruthStore.cs:21`](../Jellyfin.Plugin.JRay/Services/ManagedTruthStore.cs#L21) — `public sealed class ManagedTruthStore : IManagedTruthStore` + +### JR-010 + +**Locations:** 5 + +- [`Jellyfin.Plugin.JRay/Controllers/ActorsController.cs:26`](../Jellyfin.Plugin.JRay/Controllers/ActorsController.cs#L26) — `public class ActorsController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Models/TruthProvenance.cs:38`](../Jellyfin.Plugin.JRay/Models/TruthProvenance.cs#L38) — `public class TruthProvenance` +- [`Jellyfin.Plugin.JRay/Services/ManagedTruthStore.cs:21`](../Jellyfin.Plugin.JRay/Services/ManagedTruthStore.cs#L21) — `public sealed class ManagedTruthStore : IManagedTruthStore` +- [`Jellyfin.Plugin.JRay/Services/TruthDataService.cs:25`](../Jellyfin.Plugin.JRay/Services/TruthDataService.cs#L25) — `public sealed class TruthDataService : ITruthDataService` +- [`Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs:21`](../Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs#L21) — `public class ManagedTruthStoreTests : IDisposable` + +### JR-011 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Services/TruthDataService.cs:25`](../Jellyfin.Plugin.JRay/Services/TruthDataService.cs#L25) — `public sealed class TruthDataService : ITruthDataService` + +### JR-012 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Controllers/ActorsController.cs:26`](../Jellyfin.Plugin.JRay/Controllers/ActorsController.cs#L26) — `public class ActorsController : ControllerBase` + +### JR-013 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Controllers/ActorsController.cs:26`](../Jellyfin.Plugin.JRay/Controllers/ActorsController.cs#L26) — `public class ActorsController : ControllerBase` + +### JR-014 + +**Locations:** 4 + +- [`Jellyfin.Plugin.JRay/Controllers/ActorsController.cs:26`](../Jellyfin.Plugin.JRay/Controllers/ActorsController.cs#L26) — `public class ActorsController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Controllers/PolicyController.cs:24`](../Jellyfin.Plugin.JRay/Controllers/PolicyController.cs#L24) — `public class PolicyController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Controllers/TruthController.cs:28`](../Jellyfin.Plugin.JRay/Controllers/TruthController.cs#L28) — `public class TruthController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Controllers/WebController.cs:21`](../Jellyfin.Plugin.JRay/Controllers/WebController.cs#L21) — `public class WebController : ControllerBase` + +### JR-015 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Controllers/TasksController.cs:34`](../Jellyfin.Plugin.JRay/Controllers/TasksController.cs#L34) — `public class TasksController : ControllerBase` + +### JR-016 + +**Locations:** 4 + +- [`Jellyfin.Plugin.JRay/Controllers/PolicyController.cs:24`](../Jellyfin.Plugin.JRay/Controllers/PolicyController.cs#L24) — `public class PolicyController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Services/MediaPolicyStore.cs:20`](../Jellyfin.Plugin.JRay/Services/MediaPolicyStore.cs#L20) — `public sealed class MediaPolicyStore : IMediaPolicyStore` +- [`Jellyfin.Plugin.JRay/Services/PolicyResolver.cs:14`](../Jellyfin.Plugin.JRay/Services/PolicyResolver.cs#L14) — `public static class PolicyResolver` +- [`Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs:14`](../Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs#L14) — `public class PolicyResolverTests` + +### JR-017 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Controllers/TasksController.cs:34`](../Jellyfin.Plugin.JRay/Controllers/TasksController.cs#L34) — `public class TasksController : ControllerBase` + +### JR-018 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Controllers/CoverageController.cs:30`](../Jellyfin.Plugin.JRay/Controllers/CoverageController.cs#L30) — `public class CoverageController : ControllerBase` + +### JR-019 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Controllers/CoverageController.cs:30`](../Jellyfin.Plugin.JRay/Controllers/CoverageController.cs#L30) — `public class CoverageController : ControllerBase` + +### JR-020 + +**Locations:** 3 + +- [`Jellyfin.Plugin.JRay/Controllers/WebController.cs:21`](../Jellyfin.Plugin.JRay/Controllers/WebController.cs#L21) — `public class WebController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Services/FileTransformationRegistration.cs:31`](../Jellyfin.Plugin.JRay/Services/FileTransformationRegistration.cs#L31) — `public static class FileTransformationRegistration` +- [`Jellyfin.Plugin.JRay/Web/jray-overlay.js:14`](../Jellyfin.Plugin.JRay/Web/jray-overlay.js#L14) — `function truncate(text, maxLength)` + +### JR-021 + +**Locations:** 2 + +- [`Jellyfin.Plugin.JRay/Services/WebClientPatchService.cs:26`](../Jellyfin.Plugin.JRay/Services/WebClientPatchService.cs#L26) — `public static class WebClientPatchService` +- [`scripts/checks/no-index-injection.sh:11`](../scripts/checks/no-index-injection.sh#L11) — `Unknown` + +### JR-022 + +**Locations:** 2 + +- [`Jellyfin.Plugin.JRay/Services/WebClientPatchService.cs:26`](../Jellyfin.Plugin.JRay/Services/WebClientPatchService.cs#L26) — `public static class WebClientPatchService` +- [`Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs#L16) — `public class WebClientPatchServiceTests` + +### JR-023 + +**Locations:** 3 + +- [`Jellyfin.Plugin.JRay/Controllers/StatusController.cs:20`](../Jellyfin.Plugin.JRay/Controllers/StatusController.cs#L20) — `public class StatusController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Services/FileTransformationRegistration.cs:31`](../Jellyfin.Plugin.JRay/Services/FileTransformationRegistration.cs#L31) — `public static class FileTransformationRegistration` +- [`Jellyfin.Plugin.JRay.Tests/FileTransformationRegistrationTests.cs:20`](../Jellyfin.Plugin.JRay.Tests/FileTransformationRegistrationTests.cs#L20) — `public class FileTransformationRegistrationTests` + +### JR-024 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Web/jray-overlay.js:14`](../Jellyfin.Plugin.JRay/Web/jray-overlay.js#L14) — `function truncate(text, maxLength)` + +### JR-025 + +**Locations:** 5 + +- [`Jellyfin.Plugin.JRay/Configuration/ManifestServer.cs:75`](../Jellyfin.Plugin.JRay/Configuration/ManifestServer.cs#L75) — `public class ManifestServer` +- [`Jellyfin.Plugin.JRay/Controllers/ManifestController.cs:24`](../Jellyfin.Plugin.JRay/Controllers/ManifestController.cs#L24) — `public class ManifestController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Models/Jmanifest.cs:21`](../Jellyfin.Plugin.JRay/Models/Jmanifest.cs#L21) — `public class Jmanifest` +- [`Jellyfin.Plugin.JRay/Services/Interfaces/IManifestExchangeClient.cs:16`](../Jellyfin.Plugin.JRay/Services/Interfaces/IManifestExchangeClient.cs#L16) — `public interface IManifestExchangeClient` +- [`Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs:32`](../Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs#L32) — `public class ManifestExchangeClient : IManifestExchangeClient, IDisposable` + +### JR-027 + +**Locations:** 2 + +- [`Jellyfin.Plugin.JRay/Models/Jmanifest.cs:21`](../Jellyfin.Plugin.JRay/Models/Jmanifest.cs#L21) — `public class Jmanifest` +- [`Jellyfin.Plugin.JRay/Services/ManifestValidator.cs:26`](../Jellyfin.Plugin.JRay/Services/ManifestValidator.cs#L26) — `public static class ManifestValidator` + +### JR-028 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs:282`](../Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs#L282) — `private static async Task ReadCappedAsync(` + +### JR-029 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs:32`](../Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs#L32) — `public class ManifestExchangeClient : IManifestExchangeClient, IDisposable` + +### JR-030 + +**Locations:** 2 + +- [`Jellyfin.Plugin.JRay/Services/ManifestConverter.cs:11`](../Jellyfin.Plugin.JRay/Services/ManifestConverter.cs#L11) — `public static class ManifestConverter` +- [`Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs:32`](../Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs#L32) — `public class ManifestExchangeClient : IManifestExchangeClient, IDisposable` + +### JR-031 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Controllers/ManifestController.cs:24`](../Jellyfin.Plugin.JRay/Controllers/ManifestController.cs#L24) — `public class ManifestController : ControllerBase` + +### JR-036 + +**Locations:** 2 + +- [`Jellyfin.Plugin.JRay/Configuration/PluginConfiguration.cs:16`](../Jellyfin.Plugin.JRay/Configuration/PluginConfiguration.cs#L16) — `public class PluginConfiguration : BasePluginConfiguration` +- [`Jellyfin.Plugin.JRay/Models/TruthProvenance.cs:38`](../Jellyfin.Plugin.JRay/Models/TruthProvenance.cs#L38) — `public class TruthProvenance` + +### JR-037 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs:32`](../Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs#L32) — `public class ManifestExchangeClient : IManifestExchangeClient, IDisposable` + +### JR-038 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Configuration/PluginConfiguration.cs:16`](../Jellyfin.Plugin.JRay/Configuration/PluginConfiguration.cs#L16) — `public class PluginConfiguration : BasePluginConfiguration` + +### JR-041 + +**Locations:** 1 + +- [`scripts/checks/no-gallery-data.sh:14`](../scripts/checks/no-gallery-data.sh#L14) — `Unknown` + +### JR-042 + +**Locations:** 3 + +- [`Jellyfin.Plugin.JRay/Services/AudioSignature.cs:49`](../Jellyfin.Plugin.JRay/Services/AudioSignature.cs#L49) — `public static class AudioSignature` +- [`Jellyfin.Plugin.JRay/Services/AudioSignatureService.cs:39`](../Jellyfin.Plugin.JRay/Services/AudioSignatureService.cs#L39) — `public class AudioSignatureService` +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs:32`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs#L32) — `public class AudioSignatureTests` + +### JR-043 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs:32`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs#L32) — `public class AudioSignatureTests` + +### JR-044 + +**Locations:** 2 + +- [`Jellyfin.Plugin.JRay/Services/AudioSignatureMatcher.cs:24`](../Jellyfin.Plugin.JRay/Services/AudioSignatureMatcher.cs#L24) — `public static class AudioSignatureMatcher` +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs:26`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs#L26) — `public class AudioSignatureMatcherTests` + +### JR-045 + +**Locations:** 2 + +- [`Jellyfin.Plugin.JRay/Services/AudioSignatureMatcher.cs:24`](../Jellyfin.Plugin.JRay/Services/AudioSignatureMatcher.cs#L24) — `public static class AudioSignatureMatcher` +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs:26`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs#L26) — `public class AudioSignatureMatcherTests` + +### PR-001 + +**Locations:** 3 + +- [`Jellyfin.Plugin.JRay/Controllers/WebController.cs:21`](../Jellyfin.Plugin.JRay/Controllers/WebController.cs#L21) — `public class WebController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Models/TruthProvenance.cs:38`](../Jellyfin.Plugin.JRay/Models/TruthProvenance.cs#L38) — `public class TruthProvenance` +- [`Jellyfin.Plugin.JRay/Services/TruthDataService.cs:25`](../Jellyfin.Plugin.JRay/Services/TruthDataService.cs#L25) — `public sealed class TruthDataService : ITruthDataService` + +### PR-003 + +**Locations:** 5 + +- [`Jellyfin.Plugin.JRay/Controllers/CoverageController.cs:30`](../Jellyfin.Plugin.JRay/Controllers/CoverageController.cs#L30) — `public class CoverageController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Controllers/PolicyController.cs:24`](../Jellyfin.Plugin.JRay/Controllers/PolicyController.cs#L24) — `public class PolicyController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Controllers/TasksController.cs:34`](../Jellyfin.Plugin.JRay/Controllers/TasksController.cs#L34) — `public class TasksController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Services/MediaPolicyStore.cs:20`](../Jellyfin.Plugin.JRay/Services/MediaPolicyStore.cs#L20) — `public sealed class MediaPolicyStore : IMediaPolicyStore` +- [`Jellyfin.Plugin.JRay/Services/PolicyResolver.cs:14`](../Jellyfin.Plugin.JRay/Services/PolicyResolver.cs#L14) — `public static class PolicyResolver` + +### PR-004 + +**Locations:** 5 + +- [`Jellyfin.Plugin.JRay/Controllers/StatusController.cs:20`](../Jellyfin.Plugin.JRay/Controllers/StatusController.cs#L20) — `public class StatusController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Services/FileTransformationRegistration.cs:31`](../Jellyfin.Plugin.JRay/Services/FileTransformationRegistration.cs#L31) — `public static class FileTransformationRegistration` +- [`Jellyfin.Plugin.JRay/Services/ManagedTruthStore.cs:21`](../Jellyfin.Plugin.JRay/Services/ManagedTruthStore.cs#L21) — `public sealed class ManagedTruthStore : IManagedTruthStore` +- [`Jellyfin.Plugin.JRay/Services/WebClientPatchService.cs:26`](../Jellyfin.Plugin.JRay/Services/WebClientPatchService.cs#L26) — `public static class WebClientPatchService` +- [`scripts/checks/no-index-injection.sh:11`](../scripts/checks/no-index-injection.sh#L11) — `Unknown` + +### PR-005 + +**Locations:** 3 + +- [`Jellyfin.Plugin.JRay/Configuration/ManifestServer.cs:75`](../Jellyfin.Plugin.JRay/Configuration/ManifestServer.cs#L75) — `public class ManifestServer` +- [`Jellyfin.Plugin.JRay/Configuration/PluginConfiguration.cs:16`](../Jellyfin.Plugin.JRay/Configuration/PluginConfiguration.cs#L16) — `public class PluginConfiguration : BasePluginConfiguration` +- [`Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs:32`](../Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs#L32) — `public class ManifestExchangeClient : IManifestExchangeClient, IDisposable` + +### PR-006 + +**Locations:** 4 + +- [`Jellyfin.Plugin.JRay/Configuration/ManifestServer.cs:75`](../Jellyfin.Plugin.JRay/Configuration/ManifestServer.cs#L75) — `public class ManifestServer` +- [`Jellyfin.Plugin.JRay/Controllers/ManifestController.cs:24`](../Jellyfin.Plugin.JRay/Controllers/ManifestController.cs#L24) — `public class ManifestController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Services/Interfaces/IManifestExchangeClient.cs:16`](../Jellyfin.Plugin.JRay/Services/Interfaces/IManifestExchangeClient.cs#L16) — `public interface IManifestExchangeClient` +- [`Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs:32`](../Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs#L32) — `public class ManifestExchangeClient : IManifestExchangeClient, IDisposable` + +### SR-001 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay/Models/TruthActor.cs:20`](../Jellyfin.Plugin.JRay/Models/TruthActor.cs#L20) — `public class TruthActor` + +### SR-002 + +**Locations:** 7 + +- [`Jellyfin.Plugin.JRay/Controllers/ActorsController.cs:26`](../Jellyfin.Plugin.JRay/Controllers/ActorsController.cs#L26) — `public class ActorsController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Models/ActorInScene.cs:15`](../Jellyfin.Plugin.JRay/Models/ActorInScene.cs#L15) — `public class ActorInScene` +- [`Jellyfin.Plugin.JRay/Models/TruthActor.cs:20`](../Jellyfin.Plugin.JRay/Models/TruthActor.cs#L20) — `public class TruthActor` +- [`Jellyfin.Plugin.JRay/Models/TruthScene.cs:21`](../Jellyfin.Plugin.JRay/Models/TruthScene.cs#L21) — `public class TruthScene` +- [`Jellyfin.Plugin.JRay/Services/ManifestConverter.cs:11`](../Jellyfin.Plugin.JRay/Services/ManifestConverter.cs#L11) — `public static class ManifestConverter` +- [`Jellyfin.Plugin.JRay/Services/PresenceLookup.cs:27`](../Jellyfin.Plugin.JRay/Services/PresenceLookup.cs#L27) — `public static class PresenceLookup` +- [`Jellyfin.Plugin.JRay/Web/jray-overlay.js:14`](../Jellyfin.Plugin.JRay/Web/jray-overlay.js#L14) — `function truncate(text, maxLength)` + +### SR-003 + +**Locations:** 11 + +- [`Jellyfin.Plugin.JRay/Controllers/TruthController.cs:28`](../Jellyfin.Plugin.JRay/Controllers/TruthController.cs#L28) — `public class TruthController : ControllerBase` +- [`Jellyfin.Plugin.JRay/Models/Jmanifest.cs:21`](../Jellyfin.Plugin.JRay/Models/Jmanifest.cs#L21) — `public class Jmanifest` +- [`Jellyfin.Plugin.JRay/Models/TruthCut.cs:15`](../Jellyfin.Plugin.JRay/Models/TruthCut.cs#L15) — `public class TruthCut` +- [`Jellyfin.Plugin.JRay/Models/TruthExtraction.cs:21`](../Jellyfin.Plugin.JRay/Models/TruthExtraction.cs#L21) — `public class TruthExtraction` +- [`Jellyfin.Plugin.JRay/Models/TruthFile.cs:26`](../Jellyfin.Plugin.JRay/Models/TruthFile.cs#L26) — `public class TruthFile` +- [`Jellyfin.Plugin.JRay/Models/TruthScene.cs:21`](../Jellyfin.Plugin.JRay/Models/TruthScene.cs#L21) — `public class TruthScene` +- [`Jellyfin.Plugin.JRay/Services/AudioSignature.cs:49`](../Jellyfin.Plugin.JRay/Services/AudioSignature.cs#L49) — `public static class AudioSignature` +- [`Jellyfin.Plugin.JRay/Services/AudioSignatureMatcher.cs:24`](../Jellyfin.Plugin.JRay/Services/AudioSignatureMatcher.cs#L24) — `public static class AudioSignatureMatcher` +- [`Jellyfin.Plugin.JRay/Services/AudioSignatureService.cs:39`](../Jellyfin.Plugin.JRay/Services/AudioSignatureService.cs#L39) — `public class AudioSignatureService` +- [`Jellyfin.Plugin.JRay/Services/ManifestConverter.cs:11`](../Jellyfin.Plugin.JRay/Services/ManifestConverter.cs#L11) — `public static class ManifestConverter` +- [`Jellyfin.Plugin.JRay/Services/TruthSchema.cs:35`](../Jellyfin.Plugin.JRay/Services/TruthSchema.cs#L35) — `public static class TruthSchema` + +### SR-004 + +**Locations:** 3 + +- [`Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs:282`](../Jellyfin.Plugin.JRay/Services/ManifestExchangeClient.cs#L282) — `private static async Task ReadCappedAsync(` +- [`Jellyfin.Plugin.JRay/Services/ManifestValidator.cs:26`](../Jellyfin.Plugin.JRay/Services/ManifestValidator.cs#L26) — `public static class ManifestValidator` +- [`Jellyfin.Plugin.JRay/Web/jray-overlay.js:14`](../Jellyfin.Plugin.JRay/Web/jray-overlay.js#L14) — `function truncate(text, maxLength)` + +### SR-005 + +**Locations:** 1 + +- [`scripts/checks/no-gallery-data.sh:14`](../scripts/checks/no-gallery-data.sh#L14) — `Unknown` + +### UT-001 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs#L16) — `public class WebClientPatchServiceTests` + +### UT-002 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs#L16) — `public class WebClientPatchServiceTests` + +### UT-003 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs#L16) — `public class WebClientPatchServiceTests` + +### UT-004 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs#L16) — `public class WebClientPatchServiceTests` + +### UT-005 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/WebClientPatchServiceTests.cs#L16) — `public class WebClientPatchServiceTests` + +### UT-007 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs:14`](../Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs#L14) — `public class PolicyResolverTests` + +### UT-008 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs:14`](../Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs#L14) — `public class PolicyResolverTests` + +### UT-009 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs:14`](../Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs#L14) — `public class PolicyResolverTests` + +### UT-010 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs:14`](../Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs#L14) — `public class PolicyResolverTests` + +### UT-011 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs:14`](../Jellyfin.Plugin.JRay.Tests/PolicyResolverTests.cs#L14) — `public class PolicyResolverTests` + +### UT-012 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/FileTransformationRegistrationTests.cs:20`](../Jellyfin.Plugin.JRay.Tests/FileTransformationRegistrationTests.cs#L20) — `public class FileTransformationRegistrationTests` + +### UT-013 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/FileTransformationRegistrationTests.cs:20`](../Jellyfin.Plugin.JRay.Tests/FileTransformationRegistrationTests.cs#L20) — `public class FileTransformationRegistrationTests` + +### UT-014 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/FileTransformationRegistrationTests.cs:20`](../Jellyfin.Plugin.JRay.Tests/FileTransformationRegistrationTests.cs#L20) — `public class FileTransformationRegistrationTests` + +### UT-015 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/FileTransformationRegistrationTests.cs:20`](../Jellyfin.Plugin.JRay.Tests/FileTransformationRegistrationTests.cs#L20) — `public class FileTransformationRegistrationTests` + +### UT-016 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs:19`](../Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs#L19) — `public class PresenceLookupTests` + +### UT-017 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs:19`](../Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs#L19) — `public class PresenceLookupTests` + +### UT-018 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs:19`](../Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs#L19) — `public class PresenceLookupTests` + +### UT-019 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs:19`](../Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs#L19) — `public class PresenceLookupTests` + +### UT-020 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs:19`](../Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs#L19) — `public class PresenceLookupTests` + +### UT-021 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs:19`](../Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs#L19) — `public class PresenceLookupTests` + +### UT-022 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs:19`](../Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs#L19) — `public class PresenceLookupTests` + +### UT-023 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs:19`](../Jellyfin.Plugin.JRay.Tests/PresenceLookupTests.cs#L19) — `public class PresenceLookupTests` + +### UT-024 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs:21`](../Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs#L21) — `public class ManagedTruthStoreTests : IDisposable` + +### UT-025 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs:21`](../Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs#L21) — `public class ManagedTruthStoreTests : IDisposable` + +### UT-026 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs:21`](../Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs#L21) — `public class ManagedTruthStoreTests : IDisposable` + +### UT-027 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs:21`](../Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs#L21) — `public class ManagedTruthStoreTests : IDisposable` + +### UT-028 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs:21`](../Jellyfin.Plugin.JRay.Tests/ManagedTruthStoreTests.cs#L21) — `public class ManagedTruthStoreTests : IDisposable` + +### UT-029 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs#L16) — `public class TruthSchemaTests` + +### UT-030 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs#L16) — `public class TruthSchemaTests` + +### UT-031 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs#L16) — `public class TruthSchemaTests` + +### UT-032 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs#L16) — `public class TruthSchemaTests` + +### UT-033 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs#L16) — `public class TruthSchemaTests` + +### UT-034 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs#L16) — `public class TruthSchemaTests` + +### UT-035 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs#L16) — `public class TruthSchemaTests` + +### UT-036 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs#L16) — `public class TruthSchemaTests` + +### UT-037 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs:16`](../Jellyfin.Plugin.JRay.Tests/TruthSchemaTests.cs#L16) — `public class TruthSchemaTests` + +### UT-038 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs:32`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs#L32) — `public class AudioSignatureTests` + +### UT-039 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs:32`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs#L32) — `public class AudioSignatureTests` + +### UT-040 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs:32`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs#L32) — `public class AudioSignatureTests` + +### UT-041 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs:32`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs#L32) — `public class AudioSignatureTests` + +### UT-042 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs:32`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs#L32) — `public class AudioSignatureTests` + +### UT-043 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs:32`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs#L32) — `public class AudioSignatureTests` + +### UT-044 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs:32`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureTests.cs#L32) — `public class AudioSignatureTests` + +### UT-045 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs:26`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs#L26) — `public class AudioSignatureMatcherTests` + +### UT-046 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs:26`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs#L26) — `public class AudioSignatureMatcherTests` + +### UT-047 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs:26`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs#L26) — `public class AudioSignatureMatcherTests` + +### UT-048 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs:26`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs#L26) — `public class AudioSignatureMatcherTests` + +### UT-049 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs:26`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs#L26) — `public class AudioSignatureMatcherTests` + +### UT-050 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs:26`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs#L26) — `public class AudioSignatureMatcherTests` + +### UT-051 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs:26`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs#L26) — `public class AudioSignatureMatcherTests` + +### UT-052 + +**Locations:** 1 + +- [`Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs:26`](../Jellyfin.Plugin.JRay.Tests/AudioSignatureMatcherTests.cs#L26) — `public class AudioSignatureMatcherTests` +