Skip to content

bench: report recall@10 with tie bounds - #83

Merged
Rinkia merged 1 commit into
masterfrom
bench/tie-aware-recall
Jul 24, 2026
Merged

Rinkia merged 1 commit into
masterfrom
bench/tie-aware-recall

Conversation

@Rinkia

@Rinkia Rinkia commented Jul 24, 2026

Copy link
Copy Markdown
Owner

The problem

detect_extension_points breaks score ties on (module, lineno) — alphabetically. When a band of equally-scored candidates straddles rank 10, the alphabet decides which labels count as hits.

Measured across all seven targets:

Target recall@10 Tie bounds Band at the cutoff
click 8.4.2 4/4 2..4 71 tied at 0.8, 6 slots
commander 14.0.2 2/2 0..2 11 tied at 0.1, 10 slots
junit-jupiter-api 5.11.3 4/7 0..7 18 tied at 1.00, 10 slots
marshmallow 4.3.0 4/6 4..5 4 tied at 0.7, 1 slot
pluggy 1.6.0 3/3 0..3 13 tied at 0.8, 10 slots
pygments 2.20.0 0/5 0..0 —
sqlalchemy 2.0.51 1/7 0..2 50 tied at 1.00, 10 slots
Aggregate 18/34 6..23 17 of 34 labels tie-decided

Half the headline is not evidence, and the regression canaries were never canaries — click's perfect 4/4 has a lower bound of 2/4, pluggy's 3/3 a lower bound of 0/3.

What this changes

score.py reports the bounds; the accept bar becomes the lower bound. Output only — the ranking is untouched and the benchmark still reads 18/34.

Two findings recorded in benchmarks/README.md

JUnit and SQLAlchemy are not the same problem. All 18 candidates tied at 1.00 in JUnit live in org.junit.jupiter.api.extension and implement Extension — every one is a genuine extension point, and the 7 labels are a partial list of 18 correct answers. The ranking put 18/18 real seams in the top 18 and scored 4/7 for it. No tiebreak signal is warranted there; there is nothing to discriminate. SQLAlchemy's 50-wide band genuinely mixes Dialect/CreateEnginePlugin/TypeDecorator with NoCursorDQLFetchStrategy and internal adapters — that one is a real ranking failure.

Rejected: the unclamped signal sum as a tiebreak. score is min(sum, 1.0), so the clamp discards how much evidence a point carried. Using the raw sum to break ties reorders only within a band — a free lever if it correlates. It is anti-correlated: 18/34 → 15/34, and ranking by signal count gives the same 15/34. Recorded so nobody re-derives it.

Test plan

  • tests/test_benchmark_scoring.py — four new cases on the pure functions (band spanning, JUnit's shape, unambiguous bands, the lower-bound accept bar). No target packages needed.
  • Full suite green.
  • python benchmarks/score.py still reports 18/34 — verified, this is an output-only change.
  • No ground_truth.json and no ranking code touched, so the labels gate is not engaged.

🤖 Generated with Claude Code

detect_extension_points breaks score ties on (module, lineno) — the alphabet.
When a band of equally-scored candidates straddles rank 10, the alphabet, not
the ranking, decides which labels count as hits. Measured across all seven
targets: 17 of 34 labels are tie-decided, so the published 18/34 is one sample
from the interval 6..23.

score.py now reports tie bounds per target and in aggregate. Two pure functions,
tie_bounds and recall_bounds, are covered by the framework-free self-check.
Output-only: the ranking is untouched and the benchmark still reads 18/34.

Two findings recorded in benchmarks/README.md:

- JUnit and SQLAlchemy are NOT the same problem. All 18 candidates tied at 1.00
  in JUnit live in org.junit.jupiter.api.extension and implement Extension —
  every one is a genuine extension point, and the 7 labels are a partial list of
  18 correct answers. That band is a metric artefact, not a ranking failure.
  SQLAlchemy's 50-wide band really does mix real seams with internal machinery.
- The unclamped signal sum is anti-correlated as a tiebreak (18/34 -> 15/34),
  as is ranking by signal count. The clamp is not hiding a usable lever.

The regression canaries were never canaries: click's 4/4 has a lower bound of
2/4 and pluggy's 3/3 a lower bound of 0/3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

MODScan attack-surface diff — modscan

No new execution sinks

New attack surface to review — NOT a vulnerability verdict. This compares enumerated execution sinks between two snapshots. It does not trace whether input reaches them (no taint analysis), match CVEs, or detect secrets. An empty diff means no new catalogued sinks — not that the change is safe.

The two snapshots enumerate the same execution sinks.

@github-actions

Copy link
Copy Markdown

MODScan extension-point diff

No breaking changes to extension points

@Rinkia
Rinkia merged commit 1249339 into master Jul 24, 2026
7 checks passed
@Rinkia
Rinkia deleted the bench/tie-aware-recall branch July 24, 2026 09:15
Rinkia added a commit that referenced this pull request Jul 24, 2026
…king (#85)

The detector breaks equal scores on (module, lineno) — alphabetically. A reader
taking the top ten off a band of eighteen equally-scored candidates was reading
the alphabet, and nothing in the output said so. PR #83 stopped the benchmark
telling itself that story; this stops the product telling it to users.

`modscan detect` now prints, above the table, how many candidates share the
score at the cutoff and how many of them are shown:

  **18 candidates score exactly 1.00, and this list shows 12 of them.** The 6
  left out are not ranked lower — ties are broken by module name, so the cut
  through this band is alphabetical, not evidence-based.

When the band is fully visible the note says so instead of claiming truncation,
and when the cutoff is unambiguous there is no note at all — a warning that
fires on every run is one nobody reads.

`--json` gains `tied_with` per point, since a machine consumer that re-ranks or
truncates the payload needs the same fact and cannot read Markdown. The field is
additive; `modscan diff` compares ids, category and kind, not the payload shape.

Ties are measured on the full ranked list before `--limit` cuts it — computing
them afterwards would describe the truncated list rather than the ranking.

Presentation only: no weight, no signal and no ordering changed. Benchmark
unchanged at 18/34, bounds 6..23.

examples/detect-markdown.md is regenerated from its committed recipe. It was
already stale — it predated the MS-MOD-* signal ids from #56 — and it turns out
to be the exact failure this change fixes: twelve rows shown out of eighteen
tied at 1.00.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Rinkia added a commit that referenced this pull request Jul 24, 2026
…86)

Measuring ties in #83 exposed that every target here scores its labels inside a
tied band. click's perfect 4/4 has a lower bound of 2/4; pluggy's 3/3 has a lower
bound of 0/3. A benchmark of seven targets had, in effect, no regression guard —
the two designated canaries could not falsify anything on their own.

flask 3.1.2 was picked by measuring candidate distributions first, not by taste.
Its ranking is graded rather than flat — 3 candidates at 1.00, 6 at 0.90, then
exactly ONE at the rank-10 cutoff — so its recall@10 has no tie bounds at all:
worst case equals best case. It is the only target of the eight whose number is
pure evidence.

  flask 3.1.2 | 98 candidates | 6 labels | ranks 2, 3, 10, 38, 39, 98 | 3/6

Aggregate 18/34 -> 21/40, bounds 6..23 -> 9..26. All six new labels are tie-free,
so the tie-decided share falls from 17/34 to 17/40.

What it already says, recorded in benchmarks/README.md:

- MethodView ranks 98 of 98, dead last. Arguably Flask's most-used class-based
  extension point, and every existing signal is blind to it.
- DefaultJSONProvider ranks 1st while JSONProvider — the class the docstring
  explicitly says to subclass — ranks 10th. Role-suffix and base-role reward the
  concrete implementation over the contract it implements.
- Dynamic imports take ranks 4-9 and push Flask and Blueprint below them: the
  pygments failure again, on a package with no plugin registry at all.

Labels quote the docstrings shipped in the 3.1.2 wheel, per the rule about
preferring docs pinned to the measured version. Two exclusions are recorded with
their reasons: Flask itself (its shipped docstring never invites subclassing) and
Blueprint (you construct one and register the instance — calling an API).

Flask's largest extension surface, the before_request / errorhandler /
template_filter decorators, cannot be labelled at all: they are methods on the
app object and the parser only takes top-level defs as seams. That blind spot is
now written down in the labels file instead of being an unwritten limitation.

Labels only — no ranking code touched, as the gate requires.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@Rinkia Rinkia mentioned this pull request Sep 8, 2026
4 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant