fix(experts): scope-filter claims before ranking entities - #689
fix(experts): scope-filter claims before ranking entities#689dripsmvcp wants to merge 2 commits into
Conversation
`rank_experts` filtered candidate claims on status alone, so a private or cross-project claim contributed to every number in the row and had its id handed back under `top_claim_ids` — to a viewer that cannot retrieve the claim itself. `kb.recall`, `kb.search`, `kb.context`, the salience sidebar and `kb.explain_ranking` all scope-filter first; `kb.experts` did not. this is the same defect `detect_themes` had (issue vouchdev#657), on the sibling read surface, and it takes the same fix: resolve a `ViewerContext` (the config-resolved one when the caller passes none, so a KB read with no explicit viewer reads as its own project) and drop anything `is_visible` rejects before aggregating. filtering only the ids would not have been enough. `claim_count`, `citation_count` and `score` are all computed over the same set, so an entity whose evidence is mostly private would keep outranking one whose evidence the viewer can actually read, and `min_claims` would keep being cleared on claims the caller will never see. the fts candidate fetch now goes through `scoped_fetch_limit`, matching kb.search — otherwise a KB whose top hits are mostly private starves the pool of the claims this viewer can actually see. viewer scope is plumbed through all three surfaces: `project`/`agent` on the mcp tool, `viewer_from_params` in the jsonl handler (flat or nested `viewer_scope`), and `--project`/`--agent` on the cli mirror.
the diff-coverage gate wants 100% of changed python, and the mcp tool was import-covered only — the decorator ran, the body never did. two cases, matching what the tool actually promises: a plain ranking, and the viewer scope this change exists for, where a private claim is invisible to another agent and visible to its owner.
|
the reasoning for filtering before aggregating rather than only scrubbing routing the FTS candidate fetch through mirroring good to see one question rather than an objection: |
closes #684
what
experts.rank_expertsfiltered candidate claims on status alone:no
is_visiblecheck, so a private or cross-project claim contributed toevery number in the row and had its id handed back under
top_claim_ids.kb.recall,kb.search,kb.context, the salience sidebar andkb.explain_rankingall scope-filter first;kb.expertsdid not.the fix
rank_expertsnow takes an optionalviewer: ViewerContext, defaulting toviewer_from(config_path=store.config_path)— so a KB read with no explicitviewer reads as its own project — and drops anything
is_visiblerejectsbefore aggregating. this mirrors
detect_themesexactly, which had the samedefect on the sibling read surface (#657).
filtering only
top_claim_idswould not have been enough.claim_count,citation_countandscoreare computed over the same set, so an entitywhose evidence is mostly private would keep outranking one whose evidence the
viewer can actually read, and
min_claimswould keep being cleared on claimsthe caller never gets to see.
the fts candidate fetch goes through
scoped_fetch_limitnow, matchingkb.search — otherwise a KB whose top hits are mostly private starves the
candidate pool of the claims this viewer can see.
surfaces
viewer scope is plumbed through all three, so a transport that knows who is
asking can say so:
kb_experts—project/agentkeyword params, as onkb_audit_h_experts—viewer_from_params, so both flatproject/agentand nested
viewer_scopeworkvouch experts—--project/--agent, worded as onvouch searchtests
five added to
tests/test_experts.py, built on the issue's repro (one publicclaim, three
private/aliceclaims, all onacme-example):claim_count: 1and only['pub']— the rankingitself, not just the ids
min_claims=2yields no row for that foreign viewer, and still yields onefor the owning agent
visibility: projectclaims from another project are excludedparams.agentfull gate green locally:
pytest tests/ --ignore=tests/embeddings,mypy src,ruff check src tests.