Files
scene-actor-extraction/docs/traceability.md
T
dtourolleandClaude Opus 5 f0c7126f80 feat: TrackRegistry — presence follows track extent
The spine of the redesign. Presence is now the extent of a track an actor owns,
[first_seen, last_seen], rather than the subset of frames in which recognition
happened to succeed. An actor recognised only at the end of a long track is
present for all of it, which is what the scene-scoped ground truth actually
records.

AR-013 — `last_seen` as an optional carries the entire liveness state: unset
means on screen, set means went off at that timestamp and still revivable,
reaped means emitted and erased. No missing-frame counter, no expired flag. It
subsumes the tracker's existing two-pool split, so there is no separate revival
path — matching a dormant track is ordinary inter-frame association.

The asymmetry is the point: interior gaps are claimed, the trailing cool-down is
not. A face lost and re-associated within the timeout never closed its track, so
the gap is presence — someone briefly occluded has not left the scene. But a
track that dies ends at its last sighting, never at the death time. That is
precisely the over-claim the retired extinction_sec keep-alive produced, where
presence ran on into the closing credits.

AR-014 — a belief swap A→B closes the track and opens a successor at the swap
frame. Not a correction: two non-twins both clearing the threshold on one face
is not realistic, whereas a track_id carried across a viewpoint change onto a
different person is. Treating it as a swap-and-continue would emit one window
blending two people; treating it as a boundary yields two that are each right.

AR-015 — two live tracks owned by one actor means at least one is wrong, since a
person cannot be in two places at once. A reverse index catches it on the update
that causes it rather than by scanning. This makes identity a third cut
detector, independent of the histogram and TransNetV2 and firing where those
failed.

AR-016 — flush() closes tracks still live at EOF. Without it a film ending
mid-shot silently drops its closing cast, which presents as a recognition miss
rather than a bookkeeping bug.

Reaping hands the dead track to the aggregator and erases it, so the registry
holds only live tracks and its size is bounded by concurrent on-screen faces
rather than growing with the film.

Locking: a frame's association pass is atomic as a unit via FrameScope, since
per-call locking would let another thread observe a half-updated frame.
owner() reads tally and verdict under one lock — separately, a track could be
both unowned and owned within a single promotion decision. A vote for an
already-reaped track is dropped and counted, because a nonzero count means the
timeout is shorter than the matcher's lag.

11 unit tests, driven directly against the registry with no network and no
fixture — the awkward cases are constructed rather than hunted for. Suite: 75
cases, 3236 assertions. Coverage 14/63 to 20/63.

Not yet wired into FaceTrackerFunc; that is AR-007/AR-008.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

TRACES: AR-012, AR-013, AR-014, AR-015, AR-016, AR-017 | SR-002
2026-07-31 09:08:39 +02:00

33 KiB
Raw Blame History

Requirements traceability matrix

Generated: 2026-07-31T07:07:04+00:00

Denominators are read from 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, T3, static).

Summary

Metric Value
Source files scanned 94
TRACES tags found 76
EXCEPTION tags found 0
Requirements defined 63
Requirements covered 20
Coverage 31.7% (20/63)
Coverage of CI-executable scope 38.5% (20/52)
Tagged but unexecuted in CI 3
Orphan tags 0

By type

Type Covered Tagged but unexecuted Defined
AR 9 0 27
DP 2 0 8
IR 6 0 8
GR 3 0 9
VR 0 3 11
  • UT tags present (separate taxonomy, not counted in coverage): UT-001, UT-101, UT-102, UT-103, UT-104
  • PR tags present (separate taxonomy, not counted in coverage): PR-002, PR-004
  • SR tags present (separate taxonomy, not counted in coverage): SR-001, SR-002, SR-003, 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
AR-027 T4 no Throughput acceptable for arbitrary gallery size
VR-001 out-of-ci yes HDF5 post-inference dump at the embedded-frame boundary
VR-002 out-of-ci yes Replay drives the real KPN nodes, not a reimplementation
VR-003 out-of-ci yes Scoring: micro-F1 against X-Ray, precision/recall logged at every eva…
VR-004 out-of-ci no Reproducible validation corpus with ground truth
VR-005 out-of-ci no Minimum face size study — TPI/FPI vs probe size, gallery held at nati…
VR-006 out-of-ci no Re-tune scene_threshold once native-rate decode lands
VR-007 out-of-ci no Expansion band, clustering threshold, and deferred-pass ablation
VR-008 out-of-ci no Gallery scaling benchmark — throughput vs gallery size
VR-010 out-of-ci no Dump provenance attributes — embedder model, detector settings, `dens…
VR-011 out-of-ci no Rewrite the replay harness for the post-AR-012 output contract

Tagged but unexecuted: VR-001, VR-002, VR-003 — 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 <reason>). 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
AR-001 Done T3 SR-002 covered src/nodes/face_detector_node.hpp Detect faces in sampled frames; emit bbox, confidence, 5-point landma…
AR-002 Planned T2 SR-002 untagged - Minimum face size 66×66 px, expressed in original resolution (dec…
AR-003 Planned T1, T2, T4 SR-002 untagged - No fixed per-frame face cap — crowd scenes must not lose background c…
AR-004 Planned T1, T4 SR-002 untagged - Backpressure: unbounded faces/frame absorbed by slowing, never by dro…
AR-005 Done T1, T3 SR-002 covered src/face_utils.hpp Align to 112×112 via ArcFace 5-point similarity transform
AR-006 Done T3 SR-002 untagged - 512-d L2-normalised embeddings, batched
AR-007 In Progress T2 SR-002 untagged - Associate detections by IoU + embedding, with frame-dependent wei…
AR-008 Planned T2 SR-002 untagged - One track pool keyed on last_seen; no separate revival path
AR-009 Done T2 SR-002 untagged - Camera-cut detection (histogram) as an association hint
AR-010 Not started — `… T2 SR-002 untagged - Scene-boundary detection (TransNetV2) as an association hint
AR-011 Planned T1, T2 SR-002 untagged - Every model is fed the input it was trained for — cost reduced by…
AR-012 Planned T2 SR-002 covered src/track_registry.hpp, tests/test_track_registry.cpp Presence follows track extent, not per-frame recognition
AR-013 Planned T2 SR-002 covered src/track_registry.hpp, tests/test_track_registry.cpp last_seen optional state machine; window ends at last sighting, nev…
AR-014 Planned T2 SR-002 covered src/track_registry.hpp, tests/test_track_registry.cpp Belief swap A→B terminates the track and starts a new one
AR-015 Planned T2 SR-002 covered src/track_registry.hpp, tests/test_track_registry.cpp Two live tracks owned by one actor ⇒ treat as a detected cut, re-asso…
AR-016 Planned T2 SR-002 covered src/track_registry.hpp, tests/test_track_registry.cpp All tracks closed at EOF — a film ends with faces on screen
AR-017 Planned T1, T2 SR-002 covered src/track_registry.hpp, tests/test_track_registry.cpp Every presence claim carries its belief and identification route
AR-018 Planned T1, T2 SR-005 untagged - Per-subject embedding store with banded admission (novel enough, safe…
AR-019 In Progress T2 SR-005 untagged - Per-film gallery annex from owned tracks; acquires the non-frontal vi…
AR-020 Planned T2 SR-005 untagged - Deferred re-identification of unknown tracks against the final expand…
AR-021 Planned T2 SR-005 untagged - Cluster unknown tracks into one entity per person, under temporal can…
AR-022 Planned T1, T2 §4 untagged - Capture still-unidentified tracks: embeddings, metadata, **context cr…
AR-023 Done T1 SR-002 covered src/gallery/gallery_calibration.hpp Fit sigmoid calibration from intra/inter similarity distributions
AR-024 Planned T1, static SR-002 untagged - Always the calibrated probability, never a raw cosine — exception…
AR-025 Planned T1 SR-002 untagged - Per-track Bayesian accumulation in log-odds, with correlated-observat…
AR-026 In Progress T1, T4 SR-001 untagged - All similarity computed as GEMM, including annex and deferred pass
AR-027 Planned T4 SR-001 untagged - Throughput acceptable for arbitrary gallery size
DP-001 Done T1, manual PR-004 covered src/main.cpp One analysis core; modes are front-ends and must not fork pipeline lo…
DP-002 Done T1, manual PR-004 covered src/main.cpp Batch CLI over one title
DP-003 Planned T1, manual PR-004 untagged - On-demand resident service with bounded, observable queue
DP-004 Planned T1, manual PR-004 untagged - Opportunistic/idle mode: external trigger, hard stop, implicit re-que…
DP-005 Planned T1, manual PR-004 untagged - Native installer, no Docker; Fedora + Arch
DP-006 Planned T1, manual PR-003 untagged - Background incremental gallery refresh on a timer
DP-007 Planned T1, manual PR-004 untagged - CI builder image, CPU-only, pinned by tag in the Gitea container regi…
DP-008 Planned T1, manual PR-004 untagged - Builder images + release jobs per backend (cpu / cuda / rocm); ship b…
IR-001 Done T1 SR-003 covered src/nodes/result_sink_node.hpp Emit the JRay truth format as sibling .jray.json
IR-002 Planned T1 SR-003 untagged - Windows carry belief + route; extraction.* carries extinction_sec
IR-003 Planned T1 SR-003 untagged - Output written after the deferred pass, not at EOF
IR-004 Done — `src/aud… T1 SR-003 covered src/audio_signature.cpp, src/audio_signature.hpp, tests/test_audio_signature.cpp Compute the audio signature exactly per server spec §3
IR-005 Done — `tests/f… T1 SR-003 covered src/audio_signature.cpp, src/audio_signature.hpp, tests/test_audio_signature.cpp Golden-vector fixture shared with the plugin repo to prove bit-exactn…
IR-006 Done T1, manual SR-001 covered scripts/run_from_jellyfin.py Jellyfin round-trip: pull pending queue, push complete results only
IR-007 Done T1 SR-003 covered src/audio_signature.cpp, src/audio_signature.hpp, tests/test_audio_signature.cpp Media < 120 s: emit no signature, apply no sync offset — identical ru…
IR-008 Done T1 SR-003 covered src/audio_signature.cpp, src/audio_signature.hpp, tests/test_audio_signature.cpp Emit and honour the signature's own v1: version prefix
GR-001 Done T1, T3 SR-001, SR-005 covered scripts/make_jellyfin_gallery.py Build gallery from Jellyfin library cast, TMDB profile fallback
GR-002 Done T1, T3 PR-003 covered scripts/make_jellyfin_gallery.py Incremental --merge refresh without re-embedding known actors
GR-003 Planned T1, T3 SR-001 untagged - Report coverage: zero-image actors, under-referenced actors, dedup, c…
GR-004 Done — basename… T1, T3 SR-001 covered scripts/filter_gallery.py, scripts/make_gallery.py, scripts/make_jellyfin_gallery.py, scripts/movienet_eval.py, scripts/optimizer/fetch_missing_actors.py, scripts/optimizer/optimize.py, scripts/optimizer/reembed_gallery.py, scripts/optimizer/replay.py, scripts/sae_embed_loader.py, scripts/sae_gallery.py, scripts/sae_stamp.py, scripts/stamp_gallery.py, src/config.hpp, src/gallery/embedder_stamp.cpp, src/gallery/embedder_stamp.hpp, src/gallery/gallery_builder.cpp, src/gallery/gallery_store.cpp, src/kpn_bindings.cpp, src/main.cpp, src/nodes/embedding_dump_node.hpp, src/scene_preview.cpp, src/types.hpp, tests/test_gallery_store.cpp Stamp embedder identity into the gallery; hard startup error on m…
GR-005 Done T1, T3 SR-005 untagged - Gallery data never leaves the instance
GR-006 Planned T1 SR-005 untagged - Provenance tiers: baked / harvested / confirmed, distinguishable per …
GR-007 Planned T1 SR-005 untagged - Persist harvested embeddings flagged and reviewable, never silent…
GR-008 Planned T1 SR-005 untagged - Flag distributional outliers among an actor's references (poisoning g…
GR-009 TBD T1 §4 untagged - Human-confirmed associations persist and improve future extractions
VR-001 Done out-of-ci PR-002 tagged, unexecuted src/nodes/embedding_dump_node.hpp HDF5 post-inference dump at the embedded-frame boundary
VR-002 Done out-of-ci PR-002 tagged, unexecuted scripts/optimizer/replay.py Replay drives the real KPN nodes, not a reimplementation
VR-003 Done out-of-ci PR-002 tagged, unexecuted scripts/optimizer/second_score.py Scoring: micro-F1 against X-Ray, precision/recall logged at every eva…
VR-004 Done out-of-ci PR-002 untagged - Reproducible validation corpus with ground truth
VR-005 Planned out-of-ci PR-002 untagged - Minimum face size study — TPI/FPI vs probe size, gallery held at nati…
VR-006 Planned out-of-ci PR-002 untagged - Re-tune scene_threshold once native-rate decode lands
VR-007 Planned out-of-ci PR-002 untagged - Expansion band, clustering threshold, and deferred-pass ablation
VR-008 Planned out-of-ci PR-002 untagged - Gallery scaling benchmark — throughput vs gallery size
VR-009 Planned T1, out-of-ci PR-002 untagged - Verify accumulated posteriors are calibrated against held-out tracks
VR-010 Planned out-of-ci PR-002 untagged - Dump provenance attributes — embedder model, detector settings, `dens…
VR-011 Planned out-of-ci PR-002 untagged - Rewrite the replay harness for the post-AR-012 output contract

Detailed mapping

AR-001

Locations: 1

AR-005

Locations: 1

AR-012

Locations: 2

AR-013

Locations: 2

AR-014

Locations: 2

AR-015

Locations: 2

AR-016

Locations: 2

AR-017

Locations: 2

AR-023

Locations: 1

DP-001

Locations: 1

DP-002

Locations: 1

GR-001

Locations: 1

GR-002

Locations: 1

GR-004

Locations: 44

IR-001

Locations: 1

IR-004

Locations: 16

IR-005

Locations: 5

IR-006

Locations: 1

IR-007

Locations: 8

IR-008

Locations: 7

PR-002

Locations: 3

PR-004

Locations: 1

SR-001

Locations: 46

SR-002

Locations: 4

SR-003

Locations: 3

SR-005

Locations: 1

UT-001

Locations: 1

UT-101

Locations: 5

UT-102

Locations: 5

UT-103

Locations: 2

UT-104

Locations: 5

VR-001

Locations: 1

VR-002

Locations: 1

VR-003

Locations: 1

Tag diagnostics

Malformed tags:

  • scripts/filter_gallery.py:80 — {'ignored': ['— a filtered gallery holds the SAME vectors as its']}
  • scripts/make_gallery.py:181 — {'ignored': ['— stamp with the model actually loaded', 'resolved']}
  • scripts/make_jellyfin_gallery.py:456 — {'ignored': ["— --merge keeps the existing actors' vectors and"]}
  • scripts/movienet_eval.py:65 — {'ignored': ['— match() below is a bare dot product against the']}
  • scripts/optimizer/fetch_missing_actors.py:109 — {'ignored': ['— the legacy JSON gallery carries the same stamp as']}
  • scripts/optimizer/fetch_missing_actors.py:123 — {'ignored': ['— merging two galleries from different models makes']}
  • scripts/optimizer/optimize.py:202 — {'ignored': ['— every (dump', 'gallery) pair is checked ONCE here']}
  • scripts/optimizer/reembed_gallery.py:62 — {'ignored': ['— this script exists to produce a gallery in a']}
  • scripts/optimizer/replay.py:113 — {'ignored': ['— checked here', 'before any network is built', 'so a']}
  • scripts/optimizer/replay.py:252 — {'ignored': ['— promote an unprovable gallery/dump binding from a']}
  • scripts/sae_embed_loader.py:22 — {'ignored': ['— single source of truth for "which model is this"']}
  • scripts/sae_gallery.py:171 — {'ignored': ['— omitted entirely when unknown', 'so "unstamped"']}
  • scripts/sae_gallery.py:199 — {'ignored': ['— carried through so a derived gallery (filter']}