Skip to content

Path-A validators are blind to lane_challenges.score_multiplier #251

Description

@wallscaler

Context

PR #247 added `score_multiplier` on `lane_challenges` and made the publisher's weight-policy aggregator multiply `weighted_score * score_multiplier` when building the signed weight vector.

This works perfectly for Path B validators (signed-publisher-vector pull): they receive the already-multiplied scores in the wire format and apply them as-is.

Path A validators (local-compute mode) read `pulled_eval_runs` and compute weights themselves via `latest_pulled_score_per_hotkey` in `src/cathedral/validator/pull_loop.py`. That table has only `(miner_hotkey, task_type, weighted_score, schema_version, ran_at)` — no per-challenge JOIN key, no awareness of `lane_challenges` (which is a publisher-side concept the validator does not replicate).

Result: once an operator sets `score_multiplier > 1.0` (or `< 1.0`), Path A validators silently keep paying flat 1.0 per win regardless of tier. Path A and Path B diverge.

Why this matters

This is fine while everyone's `score_multiplier = 1.0` (today's state). But the whole point of #236 / #237 / #247 is to enable tier-aware payout. The first operator who sets T3 to `5.0` will see:

  • Path B validators pay 5× weight to T3 winners ✓
  • Path A validators still pay 1× weight to T3 winners ✗

Operators won't notice until they audit chain weights vs publisher's signed vector. It's a quiet correctness gap.

Options

A. Bake `score_multiplier` into the wire format

Publisher's `/v1/leaderboard/recent` projection already touches `eval_runs`. Extend it to JOIN `lane_challenges` and emit `weighted_score_after_multiplier` (or just multiply server-side and emit a pre-multiplied `weighted_score`). Path A validators consume it unchanged and the multiplier flows through. Simple, no validator code change required for adoption.

B. Validator fetches multiplier separately

New endpoint `/v1/synthetic-boolean/challenge-multipliers` returns `{challenge_id: multiplier}` map. Path A validators add a JOIN-equivalent at aggregation time. Bigger lift on validator side, requires operator pull-and-restart.

C. Deprecate Path A

Document that tier-aware scoring is a Path B feature; recommend all operators move to remote-weight-source. Path A becomes a fallback for outages. Cleanest long-term but politically heavy — coordinate with operator community.

Recommendation

Option A — server-side pre-multiply in the wire format. The cost is one additional JOIN per leaderboard projection; the benefit is zero validator code change required for adoption, and no flag-day with operator coordination.

Acceptance

  • New PR adds the JOIN + pre-multiplied score in `/v1/leaderboard/recent` (or a sibling endpoint the validator pulls from)
  • Contract test: a row with `score_multiplier=5.0` and `weighted_score=1.0` emits `5.0` to the wire
  • Back-compat: rows with no JOIN match emit `weighted_score` unchanged
  • Document the change in RELEASES.md so operators understand validator behavior shifts on next pull

Effort

Small — one endpoint change, one contract test. <1 day.

Out of scope

  • Touching the signed schema-5 format itself (don't break existing signatures)
  • Validator-side code changes (point of the option-A approach is zero validator changes needed)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions