19aecee646fe5100164000377c2bc53adfaeff7e
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
19aecee646 |
feat(truth): schema_version 2 read path, v2 only
Replaces the v1 shape rather than accepting both. `anneal_sec` and the top-level `sample_fps` are deleted, not zeroed; `extraction` and `cut` blocks arrive; `scenes` become objects carrying belief and route, so a window records how far to trust it instead of being a bare float pair. `TruthSchema.IsSupported` is the single gate and is applied on all four read paths — sidecar, managed store load, managed PUT, and converted manifest. Previously only the controller checked, so the version the plugin claimed to require and the one it would actually parse were free to drift. Rejections name the file and the version found, so an item that looks empty is distinguishable from one that was refused. `ManifestConverter` carries belief, route and both provenance blocks through: dropping them would silently downgrade every fetched manifest against a locally extracted one. TRACES: JR-002, JR-003 | SR-003 |
||
|
|
5152f6f129 |
JR-010: record how truth data was obtained
Three routes now deliver truth data -- a sidecar, a worker push, and a fetched
manifest -- and once stored they were indistinguishable. The truth file records
nothing about how it arrived, so a locally computed sidecar and a loose-tier
manifest from a third-party server looked identical to every reader, despite
making claims of very different strength about the same item.
TruthProvenance records source, server, match tier, applied offset and caveat.
GET /Items/{itemId}/Provenance serves it, and JR-036's loose-tier caveat now
has somewhere to come from.
Two decisions carried in the code rather than assumed:
Provenance is stored BESIDE the truth file, never inside it. Injecting fields
would mean the bytes served back are not the bytes the producer wrote, which is
the property JR-004 turns on. UT-026 pins it by asserting the stored truth JSON
contains no provenance keys.
The applied offset is recorded because it is otherwise unrecoverable. Once
JR-030 shifts every window the timings look native, and nothing else would say
they had been shifted -- which matters when diagnosing an overlay that is
consistently a few seconds out.
A sidecar's provenance is derived rather than stored: it is local, and its
timestamp is the file's own. Precedence resolves through the same rule as
GetTruthAsync, because resolving it twice by different rules is how the two
would drift.
Fourth mutation check: stopping Delete from removing provenance fails UT-027
alone -- a stale record would otherwise outlive its claim and describe data the
next fetch had already replaced.
TRACES: UT-024, UT-025, UT-026, UT-027, UT-028 | JR-010
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
c04d5a3dcc |
JR-004, JR-005, JR-006: scene-scoped read path
Presence was decided by a LINQ predicate inline in the controller, so the semantics SR-002 sets were nowhere stated in code -- the read path complied by accident rather than by requirement. PresenceLookup is now the unit that decides, tagged, with the reasoning next to it. JR-004: windows are served exactly as given. UT-021 pins that [0,10] and [10,20] are not merged despite looking mergeable -- two windows mean a genuine departure and return, and collapsing them answers a different question from the one the truth file asked. UT-022 pins a byte-identical round trip. JR-005: bounds inclusive at both ends, zero-length windows are real sightings rather than degenerate ones to discard, overlaps resolve. The wording was the larger half of JR-005. The overlay rendered a bare list: it asserted nothing, but told the viewer nothing either, and the default reading of a paused frame is "these people are on screen" -- exactly what SR-002 forbids. It now carries an "In this scene" heading. ActorAtTime became ActorInScene, and README no longer contains "on screen" anywhere; it stated the forbidden reading outright in seven places, including the opening sentence. JR-006: measured rather than assumed. UT-023 builds 50 actors x 1000 windows and asserts the response is bounded by actor count, never window count. The lookup is a full scan on purpose -- an early exit on `start > t` would exploit the sortedness the format requires, but would silently under-report the moment one producer emitted windows out of order. UT-020 pins that unsorted input still resolves; WindowsAreSorted is a diagnostic, not a correctness dependency. Third mutation check: making the end bound exclusive fails UT-016 and UT-018 and nothing else. One character turns an inclusive window into a half-open one, dropping an actor at exactly the moment a scene ends. TRACES: UT-016, UT-017, UT-018, UT-019, UT-020, UT-021, UT-022, UT-023 TRACES: JR-004, JR-005, JR-006 | SR-002 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3d210b5bd3 |
Manifest fetch across the configured servers (JR-025 … JR-037)
Satisfies JRay-public-server UR-007. Servers are tried in configured order and the first result clearing the configured tier wins; first-match rather than best-match because querying every server for every item multiplies egress and leaks the library to more parties, and the ordering already encodes which source the admin prefers. Every server is untrusted, including the pre-configured community one, so a fetched manifest is re-validated against the same rules the server applies on upload: envelope version refused if unknown, identifiers format-checked, windows bounds-checked against the *local* file's runtime, belief bounded to [0, 1], control and bidi characters refused in names. Responses are capped while streaming rather than after buffering, since a hostile server can declare any Content-Length it likes. HTTPS is required away from loopback. A failing server is skipped with exponential backoff so one dead server cannot stall a sweep. The audio-tier offset is applied once, at store time, so stored truth is always in the local file's own timebase and no read path needs offset awareness. Windows are shifted, never reshaped — merging adjacent ones would answer "was a face visible" rather than "was the actor present" (SR-002). Also records why there is no `exact` tier, which was missing and led me to re-add one. The file-hash tier is withdrawn on legal grounds: a TMDB id discloses "some copy of this film", but an OpenSubtitles hash discloses "this exact release", which turns a catalogue lookup into a release-identification service and a server's database into a mapping from file fingerprints to the instances holding them. The reason now lives on MatchTier and in SPEC.md §JR-036, `TitleQuery` has no VideoHash property so there is nothing to send, and a test asserts the enum has no Exact member — the spec had still listed `exact` as a configurable tier, which is what made the removal look like an oversight. 42 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> TRACES: JR-025, JR-027, JR-028, JR-029, JR-030, JR-031, JR-036, JR-037 | PR-005, PR-006 |
||
|
|
2926740d03 |
JR-023: correct the missing-dependency logs, and test them
Both failure paths still told admins the overlay was "falling back to patching index.html on disk". JR-021 deleted that fallback, so the message was false -- and it was false in the worst place, since an admin reads it precisely when debugging a missing overlay and would go hunting for a patch that no longer exists. They now name the install URL and say the overlay is disabled while every other feature is unaffected. The not-found case is a Warning, not Information: a headline feature being off should not sit among startup chatter. UT-012..015 cover the branch. The File Transformation assembly is genuinely absent from the test host, so TryRegister exercises its real not-found path rather than a seam invented for the test. UT-015 pins that a null payload returns empty rather than throwing -- this callback runs inside another plugin's request path on every page served, so throwing would break the web client itself, not just JRay's overlay. Making those runnable needed the test project to reference Jellyfin.Controller and Jellyfin.Model without the plugin's ExcludeAssets=runtime. My earlier claim that DisableTransitiveFrameworkReferences alone sufficed was too narrow: it drops the demand for the web *framework*, but ILogger and MediaBrowser.Common live in the excluded assets, so anything past dependency-free logic failed at run time with FileNotFoundException. Both settings are needed, and the register now says so. Second mutation check: downgrading the warning to Information fails UT-013 and nothing else. Source restored and re-verified. JR-023 reaches Done for the detection half. The config-page banner stays T4 -- verifiable only against a live server. TRACES: UT-012, UT-013, UT-014, UT-015 | JR-023 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
cc973fd7c5 |
Make the test suite runnable without an ASP.NET Core runtime
The 11 tests built but could not execute: the plugin framework-references Microsoft.AspNetCore.App through Jellyfin.Controller, that reference flows into anything referencing the plugin, and the test host then demanded a web runtime even for tests that touch no web type. This box has none at any version, and Arch packages only 8 and 10 -- so "install the runtime" was neither available here nor a clean answer. DisableTransitiveFrameworkReferences drops the inherited reference. .NET resolves assemblies lazily, so pure-logic types load without it. This is a T1-tier decision rather than a workaround: requiring a web runtime to test string and rule logic is incidental coupling. T2 -- controllers and authorisation -- genuinely needs it, and belongs in a second project that keeps the reference. All 11 now pass. The suite was also checked to FAIL: removing the newline-stripping from RemoveInjection fails UT-001 and nothing else, then the source was restored and re-verified byte-identical. A suite that has only ever passed is not evidence that it tests anything. JR-016 and JR-022 therefore reach Done -- implemented and verified by tests that execute. JR-023 stays In Progress: its detection branch has no test and its config-page half is T4. TRACES: JR-016, JR-022 | PR-003, PR-004 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5159692364 |
Test project: UT-001..011 for JR-016 and JR-022
The register defined 46 requirements and zero tests, so every Done in it rested on inspection. This adds the T1 tier: an xUnit project in the solution, covering the two units whose logic is pure enough to test without a Jellyfin host. JR-022 (UT-001..006) covers the cases where removal could reach too far -- another plugin's injection, and an unmarked look-alike script tag -- because index.html is a file JRay shares. UT-001 pins the trailing newline to the tag, without which every install cycle leaves another blank line behind. JR-016 (UT-007..011) covers specificity resolution, including the prioritised series inside an ignored genre that motivated the rule, and a Series rule valued Guid.Empty, which would otherwise swallow every movie in the library. RemoveInjection is reached through InternalsVisibleTo rather than being made public: it is factored out for testability, not part of the surface. The suite BUILDS but does not RUN here. Jellyfin.Controller framework- references Microsoft.AspNetCore.App, so the test host demands it even for pure logic, and this machine has no ASP.NET Core runtime at any version -- RollForward cannot substitute for a framework that is absent entirely. Fix is to install aspnet-runtime, which the CI image needs for the same reason. So every UT here is recorded as Written, not Passing, and no requirement is promoted to Done on their strength. A test whose result nobody has seen is not evidence. TRACES: UT-001, UT-002, UT-003, UT-004, UT-005, UT-006 | JR-022 TRACES: UT-007, UT-008, UT-009, UT-010, UT-011 | JR-016 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |