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>
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
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>
jRay had no requirement IDs, so nothing in this repo could be traced to and
the CI gate had no denominator to read. The other two components had already
moved to registers; this brings the plugin level with them.
Adds docs/requirements.md with 46 permanent JR-nnn IDs, each carrying a parent
requirement, priority, status and verification tier, plus a per-requirement
verification plan. JR is flat rather than split by theme: the plugin is one
deployable with one audience, and JRay-public-server already ships UR/DR, so a
second repo using those prefixes would make UR-007 ambiguous across registers.
Rewrites SPEC.md as requirements prose with Current:/Gap: on every one. It had
drifted into a format-plus-API reference that documented schema_version 1 while
owning a format whose v2 shape was specified only in the other two repos, said
nothing about SR-002's scene-scoped semantics, and carried the manifest
exchange as a "planned" aside while its configuration classes were already
implemented. Plugin-side exchange obligations move here from the server's
spec, where they were an ownership inversion.
Adds JR-038..041 for PR-005, which had no software row in any repo -- it was
held structurally by SR-004 and GR-005 both being prohibitions, and a goal
preserved only by prohibitions is the kind that erodes unnoticed. jRay is the
component that actually opens a socket.
Tags 18 units with the requirements they satisfy. Tags name what the code
satisfies, so FileTransformationRegistration is not tagged JR-021: that
requirement is a prohibition and was still violated elsewhere when this was
written.
Vendors jray-project as a submodule for the system spec and shared gate.
TRACES: JR-001, JR-004, JR-005, JR-007, JR-008, JR-009, JR-010, JR-011
TRACES: JR-012, JR-013, JR-014, JR-015, JR-016, JR-017, JR-018, JR-019
TRACES: JR-020, JR-024, JR-025, JR-036, JR-038
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>