fix(orb): retire the orphaned review_targets ledger from every live reader (#9136, #9576, #9577) - #9578
Conversation
|
Warning ⏸️ LoopOver review result - manual review recommendedReview updated: 2026-07-28 18:28:17 UTC
Review summary Blockers
Nits — 6 non-blocking
Concerns raised — review before merging
📋 Copy for AI agents — paste into your coding agentDecision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionPartially addressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk 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.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Decision record
🟩 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.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
7ff5c83 to
56b1fd5
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9578 +/- ##
==========================================
- Coverage 89.84% 89.03% -0.81%
==========================================
Files 875 875
Lines 110980 110977 -3
Branches 26402 26403 +1
==========================================
- Hits 99706 98805 -901
- Misses 9992 11156 +1164
+ Partials 1282 1016 -266
Flags with carried forward coverage won't be shown. Click here to find out more.
|
…eader (#9136, #9576, #9577) `review_targets` has had NO writer anywhere since the 2026-06-22 convergence cutover. Verified exhaustively: zero INSERT/UPDATE/upsert in src/**, packages/**, scripts/**, or migrations/** — not dead code, not flag-gated, the write path does not exist. The only writes are test fixtures and the Grafana reporting copy, which legitimately mines the historical archive. Everything still reading it was therefore inert or expiring, with no alarm on any of it. This removes the last reader; `grep "FROM review_targets" src/` is now empty. WHAT WAS ACTUALLY BROKEN (three things, only one of which #9136 named) 1. computeCalibration read the orphaned table for its confidence curve, close distribution and disputed-close counts. 2. #9577 — a SECOND namespace mismatch, distinct from the SQL join #9136 names, and the one that actually disabled the recommender: `reverted` was built from review_audit target_ids (`owner/repo#123`) and tested against review_targets ids (`project:kind:owner/repo#123`). Never intersecting, so `!reverted.has(...)` was ALWAYS true — every merge read as kept, revertedMax always null, recommendedFloor always null, and the calibration-drift alert structurally unfireable. This would have survived any repopulation of the table. 3. #9576 — `GET /v1/internal/decision` 404'd for EVERY pull request since the cutover, while staying routed, authenticated and documented as the explain-any-verdict surface. Its audit query had the same namespace bug independently. WHAT REPLACED THEM decision_records (#8836) is the live successor: it carries the acted disposition, its reasonCode, and record_json's aiConfidence, keyed `repo_full_name || '#' || pull_number` — the SAME namespace as review_audit, so both mismatches retire together rather than being patched. A shared LATEST_DECISION_RECORD_FILTER restricts to the newest record per PR, matching that table's own latest-finalize-wins semantics. One subtlety that would have made this a no-op: review_targets spelled the field `confidence`, decision_records spells it `aiConfidence`. Reading only the new name yields null for every row and leaves the calibration exactly as dead as the orphaned table left it, so both spellings are accepted. WHAT WAS DELETED RATHER THAN REPOINTED, DELIBERATELY byStatus/byVerdict collapse into byDecision from review_audit's gate_decision rows — the disposition half of the old status vocabulary maps exactly onto merge/close/hold, and that is what terminalCount/nonTerminal/manualRate were really measuring. (manualRate also gains a correct denominator: it divided holds by a terminal count that EXCLUDED holds.) stuckRetryable, failed/failedTargets and their two Discord alerts are GONE. Those read `queued/reviewing/error/error_retryable` — PROCESSING states the cutover removed as a concept, not just a table, so nothing live records them and both alerts had been structurally unfireable for two months while reading as coverage. `failed` was also redundant: dlqCount/dlqTargets already cover "permanently failed", are live, and have their own alert. An alert that cannot fire is worse than no alert. computeStats/handleStats deleted too — the last reader, and unrouted (handleStats is exported and referenced only by a comment in routes.ts). The file-level dead-source check missed it because sibling exports from stats.ts ARE live. review_targets is dropped from checkReviewSourceFreshness: a 90-day probe on a permanently frozen table reported stale forever, a gauge no action could clear. decision_records takes its slot at review_audit's 7-day window, since it is now the source whose silence would mean these surfaces have gone dark. TESTS Two fixtures encoded the bugs as expected behaviour and are rewritten, not adjusted: calibrationEnv handed both queries the same bare ids, making the namespaces agree in the test and only there; routes-internal-decision-calibration seeded review_audit with the rowId shape production never writes. Both now use real `owner/repo#n` keys, so a namespace regression fails instead of hiding. Full suite green: 23,891 passed.
… at EOF git diff --check (the CI job) flags a new blank line at end of file. The AST deletion of computeStats/handleStats consumed their trailing newlines and left one behind.
56b1fd5 to
4637d4a
Compare
Review blocker on #9578: src/review/stats.ts deleted computeStats/handleStats outright -- along with their bearer auth, CORS handling, and the BUCKET_SQL whitelist -- rather than repointing the one review_targets query the way every other reader in this PR was repointed. handleStats is not wired to a route today; only a comment in src/api/routes.ts mentions it. But 'unrouted' and 'retired' are different states, and silently turning one into the other inside a PR about an orphaned TABLE is a scope the title does not cover. Restored, with its single review_targets read repointed to decision_records exactly as ops.ts's four reads were. Two shape changes fall out of the move and both are now stated in the code: decision_records has no project column, so the series keys on repo_full_name; and COALESCE(verdict, status) becomes -- merge|close|hold, the acted disposition ops.ts already reads for this purpose. It also needs LATEST_DECISION_RECORD_FILTER, now exported from ops.ts rather than copied: a PR accumulates one record per head sha, so counting all of them inflates every bucket by the number of times a PR was re-reviewed. The first repoint used dr.decision, a column decision_records does not have. The stubbed tests route by SQL substring and passed anyway; the one real-D1 test caught it. Added a real-D1 test for this query specifically, pinning the column names, the per-repo grouping, and the newest-record-wins filter -- the 368 test lines this PR had deleted are restored alongside it.
Review blocker on #9578: src/review/stats.ts deleted computeStats/handleStats outright -- along with their bearer auth, CORS handling, and the BUCKET_SQL whitelist -- rather than repointing the one review_targets query the way every other reader in this PR was repointed. handleStats is not wired to a route today; only a comment in src/api/routes.ts mentions it. But "unrouted" and "retired" are different states, and silently turning one into the other inside a PR about an orphaned TABLE is a scope the title does not cover. Restored, with its single review_targets read repointed to decision_records exactly as ops.ts's four reads were. Two shape changes fall out of the move and both are now stated in the code: decision_records has no `project` column, so the series keys on repo_full_name; and `COALESCE(verdict, status)` becomes `action` -- merge|close|hold, the acted disposition ops.ts already reads for this purpose. It also needs LATEST_DECISION_RECORD_FILTER, now exported from ops.ts rather than copied: a PR accumulates one record per head sha, so counting all of them inflates every bucket by the number of times a PR was re-reviewed. The first repoint used `dr.decision`, a column decision_records does not have. The stubbed tests route by SQL substring and passed anyway; the one real-D1 test caught it. Added a real-D1 test for this query specifically, pinning the column names, the per-repo grouping, and the newest-record-wins filter -- the 368 test lines this PR had deleted are restored alongside it.
4637d4a to
b7070de
Compare
Closes #9136
Closes #9576
Closes #9577
Summary
review_targetshas had no writer anywhere since the 2026-06-22 convergence cutover — verified exhaustively: zeroINSERT/UPDATE/upsert insrc/**,packages/**,scripts/**, ormigrations/**. Not dead code, not flag-gated; the write path does not exist. (The only writes are test fixtures andexport-grafana-reporting-db.sh, which legitimately mines the historical archive.)Everything still reading it was therefore inert or expiring, with no alarm on any of it. This removes the last reader —
grep "FROM review_targets" src/is now empty.What was actually broken
1.
computeCalibrationread the orphaned table for its confidence curve, close distribution and disputed-close counts.2. #9577 — a second namespace mismatch, distinct from the SQL join #9136 names, and the one that actually disabled the recommender:
Never intersecting, so
!reverted.has(...)was always true: every merge read as kept,revertedMaxConfidencealways null,recommendedFlooralways null, and the calibration-drift alert structurally unfireable. This would have survived any repopulation of the table.3. #9576 —
GET /v1/internal/decision404'd for every PR since the cutover, while staying routed, authenticated, and documented as the explain-any-verdict surface. Its audit-trail query carried the same namespace bug independently.What replaced them
decision_records(#8836) is the live successor: the acted disposition, its reasonCode, andrecord_json'saiConfidence, keyedrepo_full_name || '#' || pull_number— the same namespace asreview_audit, so both mismatches retire together rather than being patched.One subtlety that would have made this a silent no-op:
review_targetsspelled the fieldconfidence;decision_recordsspells itaiConfidence. Reading only the new name yields null for every row and leaves the calibration exactly as dead as the orphaned table left it. Both spellings are accepted.What was deleted rather than repointed — and why
Not everything had a live analogue. Splitting on that, rather than repointing all of it at something approximate:
byStatus+byVerdict→byDecisionreview_audit'sgate_decisionrows. The disposition half of the old status vocabulary maps exactly onto merge/close/hold, and that is whatterminalCount/nonTerminal/manualRatewere really measuring. The two fields were also near-duplicates of each other.manualRatestuckRetryable,failed/failedTargets+ two Discord alertsqueued/reviewing/error/error_retryable— processing states the cutover removed as a concept, not just a table. Nothing live records them, so both alerts had been structurally unfireable for two months while reading as coverage.failedwas additionally redundant withdlqCount/dlqTargets, which cover "permanently failed", are live, and have their own alert. An alert that cannot fire is worse than no alert.computeStats/handleStatshandleStatsis exported and referenced only by a comment inroutes.ts. The file-level dead-source check missed it because sibling exports fromstats.tsare live.review_targetsincheckReviewSourceFreshnessdecision_recordstakes its slot atreview_audit's 7-day window, since it is now the source whose silence would mean these surfaces have gone dark.Testing
Two fixtures encoded the bugs as expected behaviour and are rewritten rather than adjusted:
calibrationEnvhanded both queries the same bare ids ("a","b","c"), making the two namespaces agree in the test and only in the test. The suite passed for exactly as long as production was broken by their disagreement.routes-internal-decision-calibrationseededreview_audit.target_idwith therowIdshape that production never writes.Both now use real
owner/repo#nkeys, so a namespace regression fails the test instead of hiding inside it.Full suite green: 23,891 passed.
CI note
import-specifiers:checkreports 5 violations on this branch. They are pre-existing onmainand already fixed in #9573 (including one that CI caught me breaking —scripts/actionlint.tsruns undernode --experimental-strip-types, where the.tsextension is mandatory). They clear when that merges.