Requirements register, spec rewrite, and TRACES tags

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>
This commit is contained in:
2026-07-30 18:57:48 +02:00
co-authored by Claude Opus 5
parent 32f976e79f
commit 3b24fe1b3c
20 changed files with 1299 additions and 213 deletions
@@ -11,12 +11,19 @@ using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Plugin.JRay.Controllers;
/// <summary>
/// Exposes scene-actor-extraction "truth" data: which actors are on screen
/// at a given timestamp in a movie.
/// Exposes scene-actor-extraction "truth" data: which actors are present in
/// the scene at a given timestamp.
/// </summary>
/// <remarks>
/// Presence is <b>scene-scoped</b>, not instantaneous: a window is a claim about
/// scene membership, not a recognition event, so an actor who is off-camera
/// during a reverse shot is still present. Windows are served exactly as stored
/// — never merged, split or trimmed.
/// </remarks>
[ApiController]
[Route("Plugins/JRay/Items/{itemId}")]
[Authorize]
// TRACES: JR-004, JR-005, JR-012, JR-013, JR-014 | SR-002
public class ActorsController : ControllerBase
{
private readonly ITruthDataService _truthDataService;
@@ -19,9 +19,15 @@ namespace Jellyfin.Plugin.JRay.Controllers;
/// by genre), and supplies the genre/series option lists the config page's
/// rule editor needs.
/// </summary>
/// <remarks>
/// Percent done is <c>covered / (total - ignored)</c>: ignored items are
/// intentionally out of scope, so excluding a genre must not drag the figure
/// down as though it were outstanding work.
/// </remarks>
[ApiController]
[Route("Plugins/JRay/Coverage")]
[Authorize(Roles = "Administrator")]
// TRACES: JR-018, JR-019 | PR-003
public class CoverageController : ControllerBase
{
private readonly ILibraryManager _libraryManager;
@@ -13,9 +13,15 @@ namespace Jellyfin.Plugin.JRay.Controllers;
/// series, or a single item; setting a rule for a target that already has one
/// replaces it, so a target can never be both prioritised and ignored.
/// </summary>
/// <remarks>
/// Rules steer work discovery only. They never reach the read endpoints or the
/// overlay, because a rule says "don't spend compute here", not "pretend this
/// item does not exist".
/// </remarks>
[ApiController]
[Route("Plugins/JRay/Policy")]
[Authorize(Roles = "Administrator")]
// TRACES: JR-016, JR-014 | PR-003
public class PolicyController : ControllerBase
{
private readonly IMediaPolicyStore _policyStore;
@@ -19,9 +19,19 @@ namespace Jellyfin.Plugin.JRay.Controllers;
/// Lets a remote extraction worker discover which library items still need
/// to be processed.
/// </summary>
/// <remarks>
/// The sample is random so that repeated polling spreads work across the
/// backlog without the server tracking who holds what, and so two workers
/// polling concurrently mostly do not collide.
///
/// Prioritise/ignore rules are applied <b>here and only here</b> (JR-017):
/// they express "don't spend compute on this", not "pretend this does not
/// exist", so they never reach the read endpoints or the overlay.
/// </remarks>
[ApiController]
[Route("Plugins/JRay/Tasks")]
[Authorize(Roles = "Administrator")]
// TRACES: JR-015, JR-017 | PR-003
public class TasksController : ControllerBase
{
private const int DefaultLimit = 10;
@@ -12,11 +12,18 @@ namespace Jellyfin.Plugin.JRay.Controllers;
/// <summary>
/// Accepts scene-actor-extraction "truth" data pushed directly by a remote
/// extraction worker, for servers that cannot run the extraction pipeline
/// locally. See SPEC.md.
/// locally. See SPEC.md §2.
/// </summary>
/// <remarks>
/// The <c>schema_version</c> check here refuses an unrecognised version rather
/// than guessing at its shape. It is currently the only source that checks —
/// sidecar reads do not — which JR-003 requires be fixed by moving the check
/// into the shared read path.
/// </remarks>
[ApiController]
[Route("Plugins/JRay/Items/{itemId}/Truth")]
[Authorize(Roles = "Administrator")]
// TRACES: JR-003, JR-009, JR-014 | SR-003
public class TruthController : ControllerBase
{
private const int SupportedSchemaVersion = 1;
@@ -11,9 +11,14 @@ namespace Jellyfin.Plugin.JRay.Controllers;
/// Serves static client-side assets for JRay, e.g. the pause-overlay script
/// injected into the web client.
/// </summary>
/// <remarks>
/// Anonymous by necessity, not by oversight: the script tag is injected into
/// <c>index.html</c>, which is served before a user has logged in.
/// </remarks>
[ApiController]
[Route("Plugins/JRay")]
[AllowAnonymous]
// TRACES: JR-014, JR-020 | PR-001
public class WebController : ControllerBase
{
private const string OverlayScriptResource = "Jellyfin.Plugin.JRay.Web.jray-overlay.js";