Skip to content

fix(services): bound the reviewer_vote track-record read to the newest N votes - #10152

Merged
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
shin-core:fix/reviewer-vote-scan-bound-10023
Jul 31, 2026
Merged

fix(services): bound the reviewer_vote track-record read to the newest N votes#10152
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
shin-core:fix/reviewer-vote-scan-bound-10023

Conversation

@shin-core

Copy link
Copy Markdown
Contributor

What & why

loadLiveProviderTrackRecords ran an unbounded SELECT over a 90-day slice of audit_events — raw rows with a metadata_json blob each — on every block-mode dual review, and swallowed any failure into []:

"SELECT actor, target_key, metadata_json FROM audit_events WHERE event_type = ? AND created_at >= ? ORDER BY created_at ASC, id ASC"

} catch { return []; }  // silent

An oversized result set is a thrown driver error, so it lands in that catch and becomes "no track records" — which computeWouldHaveRouted reads as "below the decided floor" and returns null for, indistinguishable from the legitimate no-signal case the module's own invariant requires ("absence of a record must mean 'no measurable preference'"). So the stage-1 shadow silently stops recording exactly as the ledger grows large enough for stage 2 to be worth shipping against, and nothing says so. This is the same table whose unbounded growth already hit a size cap once (src/db/retention.ts:479-483).

The fix

  • Bound the scan to the newest REVIEWER_VOTE_SCAN_LIMIT (2,000) rows. A naive ASC … LIMIT n would keep the oldest rows and invert the latest-vote-wins dedup (orb(routing): the reviewer-vote read has no ORDER BY #9638), so the read selects newest-first in a subquery (ORDER BY created_at DESC, id DESC LIMIT ?) and re-orders the outer projection ASC, id ASC, so computeProviderTrackRecords still receives ascending order.
  • When the read returns a full page (rows.length === REVIEWER_VOTE_SCAN_LIMIT), emit one console.warn event: "reviewer_vote_scan_truncated" — a truncated corpus is observable rather than silently under-counted.
  • In the catch, emit one console.warn event: "reviewer_vote_scan_failed" (with the error) before returning [], so a read failure is distinguishable from a genuinely empty ledger. The fail-safe [] posture is unchanged.

Unchanged: computeWouldHaveRouted, ROUTING_MIN_DECIDED, recordRoutingShadow, REVIEWER_VOTE_EVENT_TYPE, CORPUS_LOOKBACK_MS, the corrupt-row skip, and the vote-shape filter.

Tests (test/unit/reviewer-routing.test.ts)

  • REGRESSION: a thrown read warns reviewer_vote_scan_failed and returns [], and recordRoutingShadow still resolves null without touching the review path.
  • Seeding REVIEWER_VOTE_SCAN_LIMIT + 5 votes: the newest vote's provider survives; a provider whose only vote is in the oldest overflow is absent — proving the newest-first bound.
  • A full-page read warns reviewer_vote_scan_truncated; a shorter read does not (both arms of the truncation branch).
  • All new assertions fail on main (unbounded read, no warns).

Validation

  • Diff coverage on src/services/reviewer-routing.ts is 100% line and branch.
  • npm run typecheck clean for these files; npm run engine-parity:drift-check passes (host-only file, not a twin); npm run dead-exports:check clean; the reviewer-routing suite (14 tests) green.
  • git diff --check clean; no schema/migration/generated-artifact change.

Closes #10023

…t N votes

loadLiveProviderTrackRecords ran an unbounded SELECT over a 90-day slice of
audit_events (raw rows, a metadata_json blob each) on every block-mode dual
review, and swallowed any failure into []. An oversized result set throws, lands
in that catch, and becomes 'no track records' — which computeWouldHaveRouted reads
as 'below the decided floor' and indistinguishable from the legitimate no-signal
case the module's invariants require. So the stage-1 shadow silently stops
recording exactly as the ledger grows large enough for stage 2 to be worth
shipping against, with nothing anywhere saying so.

Bound the scan to the newest REVIEWER_VOTE_SCAN_LIMIT rows via a newest-first
subquery re-ordered ascending in the outer projection (a naive ASC ... LIMIT would
keep the OLDEST rows and invert the latest-vote-wins dedup JSONbored#9638 established). Warn
reviewer_vote_scan_truncated at a full page and reviewer_vote_scan_failed in the
catch before failing safe, so a truncated or failed read is observable rather than
silently under-counting. computeWouldHaveRouted, the corpus lookback window, the
corrupt-row skip, and the fail-safe [] posture are all unchanged.

Closes JSONbored#10023
@shin-core
shin-core requested a review from JSONbored as a code owner July 31, 2026 09:30
@loopover-orb

loopover-orb Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-31 09:53:06 UTC

2 files · 1 AI reviewer · no blockers · readiness 98/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This PR bounds the previously-unbounded reviewer_vote track-record scan to the newest REVIEWER_VOTE_SCAN_LIMIT (2,000) rows, correctly using a DESC-ordered subquery re-projected ASC so the downstream latest-vote-wins dedup in computeProviderTrackRecords still receives ascending input. It also adds observability (console.warn) for both the truncation and read-failure cases, which distinguishes a scan failure/truncation from a genuinely empty ledger — closing the gap the description identifies where an oversized result set silently degraded to 'no evidence'. The fix is well-targeted, matches the fail-safe posture (`[]` on error, byte-identical downstream behavior), and is thoroughly tested including the newest-N-survives-oldest-drops case and both truncation-warn arms.

Nits — 5 non-blocking
  • console.warn calls in src/services/reviewer-routing.ts:98 and :121 are intentional structured logging additions (not debug leftovers) per the PR's own stated design — verify this matches the codebase's existing logging convention rather than a dedicated logger.
  • The magic numbers flagged (90 days at line 31, 200-char truncation at line 121) predate this diff's `90` usage (CORPUS_LOOKBACK_MS was already present) — only the `.slice(0, 200)` truncation length in the catch block is new and could be named as a constant, e.g. `ERROR_MESSAGE_MAX_LEN`.
  • REVIEWER_VOTE_SCAN_LIMIT = 2_000 is a reasonable but essentially arbitrary choice; the comment justifies it but there's no test proving 2,000 is actually enough for real-world vote volume over 90 days — worth a follow-up if vote volume grows.
  • The linked issue services(reviewer-routing): bound the 90-day reviewer_vote read, whose overflow is swallowed into "no evidence" #10023 is only partially covered per the external brief; confirm the issue's full scope (e.g., any additional acceptance criteria) is addressed before treating this as fully closing it.
  • Consider extracting the `200` character truncation length into a named constant near REVIEWER_VOTE_SCAN_LIMIT for consistency with the rest of the file's naming discipline (src/services/reviewer-routing.ts:121).

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #10023
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 78 registered-repo PR(s), 60 merged, 0 issue(s).
Contributor context ✅ Confirmed Gittensor contributor shin-core; Gittensor profile; 78 PR(s), 0 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: moderate
Linked issue satisfaction

Addressed
The diff adds the exported REVIEWER_VOTE_SCAN_LIMIT constant, rewrites the query to select the newest N rows via a DESC subquery re-ordered ASC in the outer projection, and adds the required console.warn calls for both truncation and read failure while preserving the fail-safe []; new tests cover the failure warn, newest-survives/oldest-dropped bounding, and the truncation-warn threshold. One deli

Review context
  • Author: shin-core
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: TypeScript, JavaScript, Solidity, Dart, Python, CSS, PHP, Rust
  • Official Gittensor activity: 78 PR(s), 0 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Keep the PR focused and include validation evidence before maintainer review.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 31, 2026

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.14%. Comparing base (8fe0f4d) to head (0600a75).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main   #10152       +/-   ##
===========================================
- Coverage   92.05%   80.14%   -11.92%     
===========================================
  Files         931      283      -648     
  Lines      114034    58781    -55253     
  Branches    27537     8688    -18849     
===========================================
- Hits       104977    47109    -57868     
- Misses       7759    11381     +3622     
+ Partials     1298      291     -1007     
Flag Coverage Δ
backend 100.00% <100.00%> (+4.31%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/services/reviewer-routing.ts 100.00% <100.00%> (ø)

... and 781 files with indirect coverage changes

@loopover-orb
loopover-orb Bot merged commit 7075e97 into JSONbored:main Jul 31, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

services(reviewer-routing): bound the 90-day reviewer_vote read, whose overflow is swallowed into "no evidence"

1 participant