a7d6ef051a
PR #285's entity grounding checked only title + snippet. That missed: - YouTube videos where the entity is mentioned in transcript but not in title (false demotion of on-topic content) - Reddit posts where the entity is in top comments but not in title (false demotion of on-topic discussion) And it also wasn't strong enough to reliably demote items like the 2026-04-19 Nate Herk "Managed Agents" video - which had no Hermes anywhere - because the -25 penalty on rerank_score composed to only -15 on final_score via the 0.60 weight, and engagement bonus partially offset that. Two fixes: 1. _candidate_haystack() now joins title + snippet + metadata[transcript_snippet] + metadata[transcript_highlights] + metadata[top_comments][*].excerpt/text + metadata[comment_insights]. Catches entity mentions wherever they actually live. Guarded with isinstance checks so malformed metadata doesn't raise. 2. ENTITY_MISS_FINAL_PENALTY (20.0) applied directly in _final_score when candidate.explanation contains "entity-miss". This lands the full penalty weight on the composite signal that cluster-scoring consumes, instead of being diluted by the rerank_score weight. Combined effect: entity-miss gap grows from ~15 to ~35 points. Tests: 8 new scenarios covering transcript match, transcript highlight match, top-comment match, comment-insight match, empty-text skip, no-primary-entity no-op, and the dual-penalty composition check.