Skip to content

atlas-eval: a duplicate query key in a run's results object silently replaces that query's ranking — validation never sees the dropped entry #6390

Closed

Description

@dezren39

atlas-eval: a duplicate query key in a run's results object silently replaces that query's ranking — validation never sees the dropped entry

pkgs/atlas-search/crates/atlas-core/src/eval.rs is the deterministic offline evaluation harness (#5741): saved rankings are scored against judged qrels, and its reports decide whether a retrieval signal earns a place in the fused ranking and which parameters select_best_signal picks. Its module docs promise strict duplicate discipline — duplicate query ids, duplicate qrels, duplicate docs in a ranking, and duplicate signal names are all rejected at parse/validation time — and EvalData::validate() (called from EvalData::from_json, eval.rs:150) enforces each of them. One duplicate class escapes, because it vanishes before validation runs:

  • Run.results (eval.rs:125) is BTreeMap<String, Vec<String>> — query id → ranked doc ids. serde's map deserialization inserts key by key, so JSON text that names the same query twice in one run's results object keeps only the last ranking; the first is silently discarded during parsing. validate() iterates the post-parse map and cannot detect that anything was dropped.
  • Every other duplicate class is detectable post-parse (vectors keep both copies; struct fields are protected by serde derive, which errors on a repeated field) — results keys are the only input that arrives through a map.

Verified on the laptop with a probe crate (~/git/fleet/probe-eval-dupkey, serde/serde_json, types mirrored field-for-field from eval.rs) against the exact shapes:

  • {"signal":"dense","results":{"q1":["a","b"],"q1":["c"]}} → parses OK; results["q1"] == ["c"] — the ["a","b"] ranking is gone, with no error at any layer.
  • Control, duplicate struct field ("format" twice) → parse error duplicate field 'format' — confirming the hole is specific to the map-typed field, not a general parser laxity.

Why it matters

The eval document is a file format (versioned fixtures such as pkgs/atlas-search/fixtures/eval-smoke.json, plus generated/hand-merged eval sets per pkgs/atlas-search/eval/README.md). A duplicated query entry — a merge artifact, a generator emitting a query twice, a hand edit — does not produce an error; it produces a wrong report: the signal is scored on the replacement ranking as if that were its output, and nDCG/recall/MRR means and select_best_signal parameter choices are computed from silently truncated evidence. The harness exists to be the trustworthy oracle for signal selection; a silent input-truncation path is exactly the failure its validation section was written to prevent (the same class the deploy registry fix #6384 addressed with an object_pairs_hook: discarded text must fail the load, not vanish).

Note the existing pinning test (validation_rejects_format_violations, eval.rs:~683) builds its inputs with serde_json::json!(...) values — a Value object cannot even represent duplicate keys, so that test style structurally cannot express this case; a regression test must feed from_json raw text.

Acceptance criteria

  • EvalData::from_json rejects, with an error naming the signal and query, any atlas-eval/v1 document whose results object repeats a query key — at any run.
  • All currently-valid documents still parse and score identically (including fixtures/eval-smoke.json and the existing test corpus); every existing validation rejection (duplicate doc in a ranking, duplicate qrel, unknown query, grade > 30, duplicate signal) still fires.
  • A raw-text regression test covers the duplicate-results-key case (not via json! values, which cannot express it).
  • The fix does not change the report format (atlas-eval-report/v1) or any metric definition.

Related: #6384 (same silently-discarded-duplicate-key class in the deploy registry; fixed with a pairs hook), #5741 (the eval harness's parent issue, closed), #6046 (eval-set/gate design that will build on this harness).

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Nov 5, 2026
  2. developing-today commented on Nov 5, 2026

    @developing-today
    Contributor
  3. added
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    app/atlas-searchpkgs/atlas-search (Atlas search service + atlas-embed)
    on Nov 5, 2026
  4. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (new-issue-creator-code-b)
    • Session / ID: cron new-issue-creator-code-b, run 2026-11-05 16:20 CST
    • Found via: filed this run
    • Task: Filed after verifying that Run.results (pkgs/atlas-search/crates/atlas-core/src/eval.rs:125), a BTreeMap, silently keeps only the last ranking when JSON text repeats a query key — validate() (eval.rs:150) sees only the post-parse map. Probe ~/git/fleet/probe-eval-dupkey on the laptop (serde/serde_json, types mirrored from eval.rs): results {"q1":["a","b"],"q1":["c"]} parses OK with q1 == ["c"]; duplicate struct-field control errors as expected.
    • Expectation: effort s — triage confirms, then a duplicate-detecting parse for the results map plus a raw-text regression test.
    • Links: queue packet packet-6390-triage.json (hidden_files)
  5. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  6. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 5, 2026
  7. 73 remaining items

  8. added
    queue/queuedHas a queued task in the Supabase agent queue
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 6, 2026
  9. dezren39 commented on Nov 6, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

  10. dezren39 commented on Nov 6, 2026

    @dezren39
    MemberAuthor

    Agent note — done: fixed and merged

  11. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app/atlas-searchpkgs/atlas-search (Atlas search service + atlas-embed)kind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobstatus:donetask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions