Skip to content

fix(scoring): median instead of mean at finalizeEvaluation, S3 threshold in shadow mode - #62

Open
Abidoyesimze wants to merge 1 commit into
InfiniteZeroFoundation:developfrom
Abidoyesimze:feature/scoring-validation
Open

fix(scoring): median instead of mean at finalizeEvaluation, S3 threshold in shadow mode#62
Abidoyesimze wants to merge 1 commit into
InfiniteZeroFoundation:developfrom
Abidoyesimze:feature/scoring-validation

Conversation

@Abidoyesimze

@Abidoyesimze Abidoyesimze commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Part 1 of task_210726_6 (issue #39).

What's actually broken here

finalizeEvaluation computes sum / votes and calls it finalAvgScore. It's a mean. MECHANISM_DESIGN §6 is explicit that the canonical score should be a median across the auditor batch, specifically because a mean lets one dishonest auditor drag the result — a median tolerates up to half the batch being dishonest (this is straight out of BlockFlow's Algorithm 1, which is where the design doc's language comes from in the first place). Fixed it: added _medianOf (simple insertion sort, batches are capped at ~10 auditors so this is fine) and switched finalizeEvaluation to use it. Kept the finalAvgScore field name as-is even though it's misleading now, mostly to avoid touching the ABI dincli reads — documented the actual semantics in the NatSpec instead.

Wrote two tests specifically to catch the mean-vs-median difference ([20,25,100] → median 25, mean would've been 48; and the symmetric case with a lowball outlier) since the existing test suite only ever used identical scores everywhere, which can't tell the two apart at all.

S3 threshold

Design doc wants the median-deviation threshold shipped "shadow mode" first — computed and visible, not gating anything — until it's been validated against real score distributions. Added s3DeviationThreshold (settable, defaults to 40 which is deliberately wide, not a tuned number) and an AuditorScoreDeviation event emitted per auditor on every finalizeEvaluation call. Doesn't touch approved or slashing at all — there's a test that specifically proves a batch still finalizes fine even when every auditor in it "exceeds threshold."

The fold-in / permutation averaging thing

MECHANISM_DESIGN §6 and the issue both talk about fold-in and permutation averaging like they're already implemented. They're not — went and checked scoring.py/auditor.py/aggregator.py line by line before writing any tests (posted what I found on #39), and contribution_mode in the default policy is literally "none". Only the eligibility gate and holdout-delta scoring actually exist.

Didn't want to build a whole contribution-scoring system inside this PR's budget, so I added a minimal reference implementation instead — mc_marginal_gain_score, sequential fold-in averaged over random permutations, same shape as the truncated_MC method in the LabeliaLabs contributivity repo the design doc references. Also added a cosine-to-consensus check since that turned out to be missing too (spec says eligibility should check update direction against the batch consensus, code never did). Both ship with pytest coverage, including one test that checks the Shapley efficiency property holds exactly (contributions should sum to the total value regardless of how many permutations you average over) and one showing duplicate updates get discounted at n_perms=1 but split fairly at high n_perms — matches the tradeoff the design doc describes.

Tests

9 new Foundry tests (median + S3), 36 new pytest tests (poison detection + the two new scoring functions above). Everything existing still passes.

…old in shadow mode

finalizeEvaluation was averaging auditor scores (sum/votes) despite the
field being called finalAvgScore and the design docs calling for a
median. A single inflated or lowballed auditor score could drag the
result either way; median tolerates up to half the batch being
dishonest, matching BlockFlow's Algorithm 1. Added _medianOf (bounded
insertion sort, batch sizes are small) and switched finalizeEvaluation
over to it.

Also added the S3 deviation threshold from the design doc, shipped
shadow-mode: computed and emitted per auditor via AuditorScoreDeviation
on every finalizeEvaluation call, but it doesn't gate approval or
trigger slashing yet -- needs empirical validation on real score
distributions first, same reasoning as the design doc gives for not
hardcoding the number.

Mapped the fold-in/permutation-averaging claims in MECHANISM_DESIGN
against the actual scoring code first (posted on InfiniteZeroFoundation#39 before writing any
tests against it) -- turned out neither exists, only the eligibility
gate and holdout-delta scoring are implemented. Added a minimal
mc_marginal_gain_score reference implementation (sequential fold-in +
permutation averaging, following the LabeliaLabs truncated_MC pattern)
plus a cosine-to-consensus check that was also missing, both with pytest
coverage -- not a full contribution-scoring rollout, just enough to
validate the mechanism and unblock the required test coverage.

Existing 4-test suite plus the 9 new median/S3 tests here all pass.
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