docs: land the registers for schema v2 and the audio signature
Status and verification rows for the six requirements landed in this branch, plus UT-029 … UT-052, and the generated traceability matrix that `docs/traceability.md` holds in the other two components but was missing here. `traces-report.json` is gitignored to match the server: the matrix is committed, the JSON report is not, since nothing reads it back. Two corrections rather than additions: `AudioSignatureTests` was tagged UT-029 … UT-035, IDs the register had already assigned to the schema tests. Renumbered to UT-038 … UT-044, matching the register, which was right — duplicate IDs defeat the point of IDs being permanent. `ReadCappedAsync` implements JR-028's response cap and carried no tag, so the requirement read as uncovered. The gate reports 0 orphan tags. TRACES: JR-002, JR-003, JR-028, JR-041, JR-042, JR-043, JR-044, JR-045 | SR-003
This commit is contained in:
+64
-16
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user