Skip to content

orb(mcp): predict_gate writes an un-deduped row per call and all of them pair against one real decision — a contributor can drive the agreement metric to 100% #9138

Description

@JSONbored

Problem

predicted_gate_calls writes one un-deduped row per call, and every one of those rows pairs against the
same eventual real decision — so the predicted-vs-live agreement metric can be driven to ~100% by the
contributor it measures.

computePredictedGateVerdict (src/mcp/server.ts:4546) — backing both loopover_predict_gate and
loopover_explain_gate_disposition — ends with recordPredictedGateCall(...), which states in-code
(src/review/predicted-gate-calls.ts:64-71) "Every call gets its own row (no dedup key)" and inserts
with a random id.

computePredictedGateAgreement (src/review/predicted-gate-agreement.ts:70-76) then pairs them, and
documents the many-to-one relationship as intentional:

Multiple predicted calls before one real PR (a contributor iterating on title/scope) all pair against
that same eventual decision … so none are dropped as duplicates.

Neither tool calls enforceToolRateLimit. That per-tool 20-per-300s cap (src/mcp/server.ts:4363) is
applied to only nine tools — all of them cheaper than this one and none of them writing a row. The only
ceiling here is the shared normal route class for /mcp: 120/min (src/auth/rate-limit.ts:25).

Trigger

A contributor calls loopover_predict_gate for their own login on an accessible repo 500 times in ~5
minutes (comfortably within 120/min), then opens one PR the real gate merges. All 500 predicted merge
rows pair against that single real merge:

pairedSamples: 500, bothMerge: 500, agreementRate: 1.0, unsafeDisagreements: 0

MIN_PAIRED_SAMPLE = 30 (predicted-gate-agreement.ts:56) is crossed by one contributor in under a
second, flipping hasSignal to true.

Impact

The maintainer-facing measure of "can the predictor be trusted" is controllable by the single party with
an incentive to inflate it, and the safety counter (unsafeDisagreements) is diluted to noise by the
same mechanism. Secondary: unbounded contributor-driven growth of predicted_gate_calls, which #9083
already flags as a table class with no usable retention index.

Requirements

  • Dedupe before pairing: keep only the latest prediction per (login, project) window, or dedupe on
    (login, project, predicted_action, date). One contributor iterating should contribute one paired
    sample, not N.
  • Add enforceToolRateLimit("loopover_predict_gate") inside computePredictedGateVerdict so both tools
    share the sibling tools' cap.
  • Re-derive the currently published agreement figure after dedup and state whether the prior number was
    computed over an inflated sample.
  • Give predicted_gate_calls a retention path.

Test Coverage Requirements

99%+ patch coverage, branch-counted. Regression test: N predictions before one real decision yield
exactly one paired sample.

Links & Resources

maintainer-only — metric integrity.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions