Skip to content

preferences: deadline elapse drops remaining candidates instead of ranking them feature-less #1049

Description

@electather

Module: preferences · Dimension: correctness · Severity: medium · Location: apps/server/src/preferences/internal/engine.ts:154

Problem

In enrichCandidates, once opts.deadlineMs passes, the outer loop breaks (line 154) and the inner slice map returns null for every remaining candidate (line 158). Those candidates are never pushed into enriched, so rankCandidates returns a SHORTER list than the input. This also drops candidates whose features are already inline on the MediaItem (featuresForCandidate line 176 rawItemToCandidateFeatures needs zero IO) — the deadline check at line 158 runs before that code path even executes, despite the RankOptions doc saying 'catalog reads unbounded' and 'Items past deadline: no features' (i.e. they should still be ranked, just without features).

Why it matters

The sole production caller, catalog/internal/recommendation-writer.ts:77-79, does ranked = rankCandidates(...) then topN = ranked.slice(0, TOP_N). When a user has a cold metadata cache and the per-row deadline elapses partway, every un-enriched candidate is silently omitted, so the persisted rec list is truncated to far fewer than TOP_N items even though plenty of candidates were supplied. A feature-less candidate would still score profileScore 0 and rank by upstream position (normUpstream), preserving list length; dropping it discards that signal entirely.

Suggested fix

When the deadline elapses, keep the candidate in enriched with whatever features are available (direct/inline extraction or an empty CandidateFeatures) rather than returning null, so the item is still ranked on upstream order. Reserve dropping for candidates that are genuinely unresolvable (no id/type).

Fable-panel validated: 3/3 votes.


Filed from automated per-module audit workflow (Opus/Sonnet research → Fable-panel decision). Unverified by a human — triage before acting.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions