Files
scene-actor-extraction/docs/traceability.md
dtourolle 24d35cbde3 fix(AR-013): reap tracks on the evidence watermark, not the tracker's clock
The registry closed a track when the *tracker's* timestamp passed
`track_extinction_sec`. But votes arrive from the matcher, which is a separate
KPN node behind a channel, and much the slower of the pair. Backpressure —
working exactly as AR-004 intends — turns that channel's depth into lag, so
the tracker's clock can be far ahead of the last frame anybody has voted on.
Tracks were therefore closed before their evidence arrived: the votes landed
on ids that no longer existed, were counted as dropped, and the track was
emitted unowned or not at all.

The symptom is the part worth remembering: **a deeper channel produced fewer
identifications, from identical input.** On the SuperHero fixture, 5 actors /
16 windows at depth 32 against 3 actors / 5 windows at depth 10322; through
the replay harness, capacity 32 gave 5 actors and 10322 gave 0. A throughput
knob was silently changing the answer, which makes every sweep tuned against
it suspect.

The fix is not to bound the channel against `track_extinction_sec` — that
makes an algorithm constant police a throughput knob and leaves the result a
function of scheduling. It is to reap on an evidence watermark: the matcher
advances it as it folds each frame in, and a track is only finished once
everything up to its extinction point has actually been voted on. Same device
`SceneBoundaries::scored_through()` uses for the AR-010 join — a consumer past
that point is asking about frames nobody has looked at yet, and the honest
answer is to wait rather than guess.

Association keeps the tracker's clock, and separating the two is the other
half. They answer different questions: "may this detection link to that
track?" is asked now, about a box seen `track_extinction_sec` ago; "is that
track finished?" cannot be answered until every vote is in. Deferring
association to the evidence clock — which deferring the erase alone did — left
retired tracks associable for as long as the matcher lagged, so a new face
re-associated onto a long-dead track and two people merged into one window.

The watermark is monotonic and only ever *delays* a reap, so no window is
extended by it: AR-013's "a window ends at the last sighting, never after" is
a property of `emit_locked`, which takes `last_seen` and never `now`.

`dropped_votes` is exposed and reported — by main at shutdown and through the
replay bindings — because this failed silently for as long as it did precisely
because nothing counted it. It warns rather than aborts: a dropped frame means
the output describes footage nobody analysed and is always wrong, while a
dropped vote degrades a claim without falsifying it, and there is no
measurement yet of how often it happens on real content.

replay.py's channel capacity stops being the whole film. It was sized that way
to dodge a PyNode overflow drop that AR-004 has since replaced with parking,
and removing backpressure that way is what made the defect above so extreme.

Tag separators in kpn_bindings.cpp corrected to pipes between requirement
types, which the traceability gate was reporting as diagnostics; the matrix is
regenerated and reports 0 orphan tags.

149/149.

TRACES: AR-004, AR-012, AR-013, AR-025 | VR-011 | SR-002 | PR-002
2026-08-08 12:08:15 +02:00

84 KiB
Raw Permalink Blame History

Requirements traceability matrix

Generated: 2026-08-08T10:06:24+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 119
TRACES tags found 239
EXCEPTION tags found 1
Requirements defined 72
Requirements covered 42
Coverage 58.3% (42/72)
Coverage of CI-executable scope 73.7% (42/57)
Tagged but unexecuted in CI 10
Orphan tags 0

By type

Type Covered Tagged but unexecuted Defined
AR 26 1 30
DP 2 0 8
IR 8 0 8
GR 5 0 9
VR 1 9 17
  • UT tags present (separate taxonomy, not counted in coverage): UT-001, UT-002, UT-003, UT-004, UT-005, UT-101, UT-102, UT-103, UT-104, UT-105, UT-106, UT-107, UT-108, UT-120, UT-121, UT-122, UT-123, UT-124, UT-130, UT-131, UT-132, UT-133, UT-134, UT-135, UT-136, UT-137, UT-138, UT-139, UT-140, UT-141
  • IT tags present (separate taxonomy, not counted in coverage): IT-001
  • 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 yes 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 yes Reproducible validation corpus with ground truth
VR-005 out-of-ci yes 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, deferred-pass ablation, **and t…
VR-008 out-of-ci no Gallery scaling benchmark — throughput vs gallery size
VR-010 out-of-ci yes Dump provenance attributes — embedder model, detector settings, `dens…
VR-011 out-of-ci yes Rewrite the replay harness for the post-AR-012 output contract
VR-012 T4, out-of-ci no Quality-knee study — TPI/FPI vs sharpness and vs pose, as VR-005 did …
VR-013 T4, out-of-ci yes Cross-source identification probe — gallery from one recording, probe…
VR-015 out-of-ci yes Per-node cost and bottleneck attribution for a run — where the time a…
VR-017 out-of-ci no Vote-lag study — how often does the matcher fall more than `track…

Tagged but unexecuted: AR-027, VR-001, VR-002, VR-003, VR-004, VR-005, VR-010, VR-011, VR-013, VR-015 — 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.

Requirement Location Reason
AR-024 src/gallery/gallery_calibration.hpp:165 this asks whether two vectors are THE SAME

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 Done — `FaceDet… T2 SR-002 covered src/nodes/face_detector_node.hpp, tests/test_face_detector_node.cpp, tests/test_replay_fixtures.cpp Minimum face size 40×40 px (VR-013 measured end to end; VR-005's …
AR-003 Done — `max_fac… T1, T2, T4 SR-002 covered src/config.hpp, src/nodes/face_detector_node.hpp, src/nodes/identity_matcher_node.hpp No fixed per-frame face cap — crowd scenes must not lose background c…
AR-004 Mostly — node o… T1, T4 SR-002 covered scripts/optimizer/replay.py, src/benchmark.hpp, src/main.cpp, src/nodes/identity_matcher_node.hpp, src/nodes/scene_detector_node.hpp, src/types.hpp, tests/test_channel_bytes.cpp, tests/test_replay_fixtures.cpp, tests/test_scene_detector_node.cpp Backpressure: unbounded faces/frame absorbed by slowing, never by dro…
AR-005 Done — `umeyama… T1, T3 SR-002 covered src/face_utils.hpp, src/nodes/face_aligner_node.hpp, tests/test_face_utils.cpp Align to 112×112 via ArcFace 5-point similarity transform, fitted by …
AR-006 Done T3 SR-002 covered src/nodes/embedder_node.hpp 512-d L2-normalised embeddings, batched
AR-007 Done — `track_a… T2 SR-002 covered src/config.hpp, src/main.cpp, src/nodes/face_tracker_node.hpp, src/scene_preview.cpp, tests/test_face_tracker.cpp Associate detections by IoU + embedding, with frame-dependent wei…
AR-008 Done — one pool… T2 SR-002 covered src/config.hpp, src/main.cpp, src/nodes/face_tracker_node.hpp, src/track_registry.hpp, tests/test_face_tracker.cpp, tests/test_track_registry.cpp One track pool keyed on last_seen; no separate revival path
AR-009 Done T2 SR-002 covered src/nodes/camera_position_change_detector_node.hpp Camera-cut detection (histogram) as an association hint
AR-010 Done — decode b… T2 SR-002 covered src/main.cpp, src/nodes/scene_boundary_annotator_node.hpp, src/nodes/scene_detector_node.hpp, src/scene_boundaries.hpp, tests/test_scene_detector_node.cpp Scene-boundary detection (TransNetV2) as an association hint
AR-011 Done — both vio… T1, T2 SR-002 covered src/config.hpp, src/nodes/scene_detector_node.hpp, src/scene_boundaries.hpp, tests/test_scene_detector_node.cpp Every model is fed the input it was trained for — cost reduced by…
AR-012 Done — `src/tra… T2 SR-002 covered src/config.hpp, src/kpn_bindings.cpp, src/main.cpp, src/nodes/frame_annotation_node.hpp, src/nodes/identity_matcher_node.hpp, src/nodes/result_sink_node.hpp, src/scene_preview.cpp, src/track_registry.hpp, tests/test_replay_fixtures.cpp, tests/test_track_registry.cpp Presence follows track extent, not per-frame recognition
AR-013 Done — `last_se… T2 SR-002 covered src/config.hpp, src/nodes/frame_annotation_node.hpp, src/nodes/identity_matcher_node.hpp, src/track_registry.hpp, tests/test_replay_fixtures.cpp, tests/test_track_registry.cpp last_seen optional state machine; window ends at last sighting, nev…
AR-014 Done — swap clo… 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 Done — reverse … 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 Done — `flush()… T2 SR-002 covered src/kpn_bindings.cpp, src/main.cpp, src/nodes/result_sink_node.hpp, src/track_registry.hpp, tests/test_track_registry.cpp All tracks closed at EOF — a film ends with faces on screen
AR-017 Done — `DeadTra… T1, T2 SR-002 covered src/config.hpp, src/nodes/result_sink_node.hpp, src/track_registry.hpp, tests/test_track_registry.cpp Every presence claim carries its belief and identification route
AR-018 Done — banded a… T1, T2 SR-005 covered src/config.hpp, src/gallery/track_gallery.hpp, src/nodes/identity_matcher_node.hpp, tests/test_track_gallery.cpp Per-subject embedding store with banded admission (novel enough, safe…
AR-019 Done — all thre… T2 SR-005 covered src/gallery/track_gallery.hpp, src/nodes/identity_matcher_node.hpp, tests/test_track_gallery.cpp 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 — and the … T1 SR-002 covered src/gallery/gallery_calibration.hpp, src/nodes/identity_matcher_node.hpp, tests/test_calibration.cpp Fit sigmoid calibration from intra/inter similarity distributions
AR-024 Done — associat… T1, static SR-002 covered scripts/ci/check_raw_cosine.py, src/config.hpp, src/evidence_discount.hpp, src/gallery/gallery_calibration.hpp, src/gallery/track_gallery.hpp, src/main.cpp, src/nodes/face_tracker_node.hpp, src/nodes/identity_matcher_node.hpp, src/scene_preview.cpp, tests/test_track_gallery.cpp Always the calibrated probability, never a raw cosine — exception…
AR-025 Done — log-odds… T1 SR-002 covered src/config.hpp, src/evidence_discount.hpp, src/kpn_bindings.cpp, src/main.cpp, src/nodes/identity_matcher_node.hpp, src/track_registry.hpp, tests/test_track_registry.cpp Per-track Bayesian accumulation in log-odds, with correlated-observat…
AR-026 In Progress — t… T1, T4 SR-001 covered src/backends/gemm_backend.cpp, src/gallery/track_gallery.hpp, src/inference/similarity.hpp, src/nodes/identity_matcher_node.hpp, tests/test_similarity.cpp, tests/test_track_gallery.cpp All similarity computed as GEMM, including annex and deferred pass
AR-027 Planned T4 SR-001 tagged, unexecuted src/backends/gemm_backend.cpp Throughput acceptable for arbitrary gallery size
AR-028 Done — filled i… T2 SR-002 covered scripts/optimizer/replay.py, src/nodes/embedding_dump_node.hpp, src/nodes/face_aligner_node.hpp, src/types.hpp, tests/test_embedding_dump.cpp, tests/test_face_utils.cpp Embedding input quality assessed and carried — every face scored …
AR-029 Done — `crop_sh… T1 SR-002 covered src/face_utils.hpp, src/nodes/face_aligner_node.hpp, tests/test_face_utils.cpp Sharpness measure on the aligned crop (scale-normalised, so it ca…
AR-030 In Progress — m… T1 SR-002 covered src/face_utils.hpp, src/nodes/face_aligner_node.hpp, tests/test_face_utils.cpp Visibility measure from the AR-001 5-point landmarks — extreme pose o…
DP-001 **Done, after a rep… T1, manual PR-004 covered scripts/optimizer/replay.py, src/kpn_bindings.cpp, src/main.cpp, src/scene_preview.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 Mostly — image … 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/kpn_bindings.cpp, src/nodes/result_sink_node.hpp Emit the JRay truth format as sibling .jray.json
IR-002 Done — `schema_… T1 SR-003 covered src/config.hpp, src/main.cpp, src/nodes/result_sink_node.hpp, src/track_registry.hpp Windows carry belief + route; extraction.* carries extinction_sec
IR-003 In Progress — s… T1 SR-003 covered src/kpn_bindings.cpp, src/main.cpp Output written after the deferred pass, not at EOF
IR-004 Done — `src/aud… T1 SR-003 covered scripts/validation/test_audio_offset.py, src/audio_bindings.cpp, 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_bindings.cpp, 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 Done — `gallery… T1, T3 SR-001 covered src/build_gallery.cpp, src/gallery/gallery_calibration.hpp, src/gallery/gallery_report.hpp, tests/test_calibration.cpp 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 covered src/gallery/gallery_store.hpp 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, tests/test_embedding_dump.cpp, tests/test_replay_fixtures.cpp HDF5 post-inference dump at the embedded-frame boundary
VR-002 Done — includin… out-of-ci PR-002 tagged, unexecuted scripts/optimizer/replay.py, src/kpn_bindings.cpp, tests/test_replay_fixtures.cpp 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 tagged, unexecuted scripts/validation/ground_truth.py Reproducible validation corpus with ground truth
VR-005 Done — knee at … out-of-ci PR-002 tagged, unexecuted scripts/validation/min_face_size.py Minimum face size study — TPI/FPI vs probe size, gallery held at nati…
VR-006 **Planned, now unbl… out-of-ci PR-002 untagged - Re-tune scene_threshold once native-rate decode lands
VR-007 **Planned — scope c… out-of-ci PR-002 untagged - Expansion band, clustering threshold, deferred-pass ablation, **and t…
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 Done — `DumpPro… out-of-ci PR-002 tagged, unexecuted src/nodes/embedding_dump_node.hpp Dump provenance attributes — embedder model, detector settings, `dens…
VR-011 Done — `sae_kpn… out-of-ci PR-002 tagged, unexecuted scripts/optimizer/replay.py, scripts/optimizer/test_sae_kpn.py, src/kpn_bindings.cpp Rewrite the replay harness for the post-AR-012 output contract
VR-012 Planned T4, out-of-ci PR-002 untagged - Quality-knee study — TPI/FPI vs sharpness and vs pose, as VR-005 did …
VR-013 In Progress — h… T4, out-of-ci PR-002 tagged, unexecuted experiments/xsource/resolution_sweep.py, experiments/xsource/verify_labels.py Cross-source identification probe — gallery from one recording, probe…
VR-014 Done — 40 rando… T2, out-of-ci PR-002 covered scripts/validation/test_audio_offset.py Audio-signature offset recovery on real content — a known trim re…
VR-015 Done — `--bench… out-of-ci PR-004 tagged, unexecuted src/backends/trt_backend.cpp, src/benchmark.hpp, src/config.hpp, src/main.cpp, tests/test_benchmark.cpp Per-node cost and bottleneck attribution for a run — where the time a…
VR-016 Planned. The hi… T2, out-of-ci PR-002 untagged - Cut-detection cadence study — is cut_threshold 0.70 meaningful …
VR-017 Planned. Channe… out-of-ci PR-002 untagged - Vote-lag study — how often does the matcher fall more than `track…

Detailed mapping

AR-001

Locations: 1

AR-002

Locations: 3

AR-003

Locations: 3

AR-004

Locations: 17

AR-005

Locations: 3

AR-006

Locations: 1

AR-007

Locations: 5

AR-008

Locations: 6

AR-009

Locations: 1

AR-010

Locations: 12

AR-011

Locations: 6

AR-012

Locations: 17

AR-013

Locations: 11

AR-014

Locations: 2

AR-015

Locations: 2

AR-016

Locations: 5

AR-017

Locations: 5

AR-018

Locations: 5

AR-019

Locations: 7

AR-023

Locations: 4

AR-024

Locations: 19

AR-025

Locations: 10

AR-026

Locations: 17

AR-027

Locations: 1

AR-028

Locations: 8

AR-029

Locations: 4

AR-030

Locations: 3

DP-001

Locations: 4

DP-002

Locations: 1

GR-001

Locations: 1

GR-002

Locations: 1

GR-003

Locations: 16

GR-004

Locations: 44

GR-005

Locations: 1

IR-001

Locations: 2

IR-002

Locations: 6

IR-003

Locations: 2

IR-004

Locations: 18

IR-005

Locations: 6

IR-006

Locations: 1

IR-007

Locations: 8

IR-008

Locations: 7

IT-001

Locations: 1

PR-002

Locations: 22

PR-004

Locations: 24

SR-001

Locations: 79

SR-002

Locations: 75

SR-003

Locations: 9

SR-005

Locations: 15

UT-001

Locations: 3

UT-002

Locations: 1

UT-003

Locations: 2

UT-004

Locations: 2

UT-005

Locations: 3

UT-101

Locations: 5

UT-102

Locations: 5

UT-103

Locations: 2

UT-104

Locations: 5

UT-105

Locations: 1

UT-106

Locations: 1

UT-107

Locations: 1

UT-108

Locations: 1

UT-120

Locations: 1

UT-121

Locations: 1

UT-122

Locations: 1

UT-123

Locations: 1

UT-124

Locations: 1

UT-130

Locations: 1

UT-131

Locations: 1

UT-132

Locations: 1

UT-133

Locations: 1

UT-134

Locations: 1

UT-135

Locations: 1

UT-136

Locations: 1

UT-137

Locations: 1

UT-138

Locations: 1

UT-139

Locations: 1

UT-140

Locations: 1

UT-141

Locations: 1

VR-001

Locations: 3

VR-002

Locations: 6

VR-003

Locations: 1

VR-004

Locations: 1

VR-005

Locations: 1

VR-010

Locations: 5

VR-011

Locations: 13

VR-013

Locations: 2

VR-014

Locations: 1

VR-015

Locations: 21