Skip to content

fix(stats): bound the accuracy denominator to the window its numerator survives in - #9768

Merged
JSONbored merged 2 commits into
mainfrom
fix/byproject-accuracy-retention-window
Jul 29, 2026
Merged

fix(stats): bound the accuracy denominator to the window its numerator survives in#9768
JSONbored merged 2 commits into
mainfrom
fix/byproject-accuracy-retention-window

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

#9718 gated accuracy on whether a reversal is observable at all, and fixed the weekly trend's Orb-folded denominator. It did not fix the third asymmetry in #9676:

merged/closed come from github_app.pr_public_surface_published — the single retention-exempt audit event type (DURABLE_AUDIT_EVENT_TYPES, src/db/retention.ts:14) — so they are lifetime and never shrink. reversal_* rows prune with the rest of audit_events at 90 days. Pairing an immortal denominator with a 90-day numerator makes 1 - reversed/decided drift toward 100% as the ledger ages, independent of real reversal behavior.

Confirmed live after #9718 deployed:

byProject accuracy: [100, 100, 100]   # on 2377 / 602 / 508 reviewed, 0 reversals

The observability gate #9718 added passed here — the reversal signal genuinely is observable on that deployment — and the ratio was still meaningless. Two different bugs; the first fix couldn't have caught this one.

The fix

A second, retention-windowed disposition query supplies the accuracy denominator. It shares both of the numerator's bounds: own-ledger only, and inside audit_events' retention window. reviewed/merged/closed keep publishing lifetime volume, which is measured and correct.

That display-vs-denominator split is the same shape #7449 established for the Orb fold and #9718 carried into the weekly trend. The #7449 snapshot this supersedes (ownLedgerMerged/ownLedgerClosed) is removed rather than left dead, with its reasoning folded into the new comment.

Boundary case, stated rather than hidden: a PR published before the window but reversed inside it contributes to the numerator and not the denominator. accuracyPct's existing clamp already handles that — it's the same shape as the reopened-auto-close case its comment already describes — and the alternative, an unbounded denominator, is the bug being fixed.

Refs #9676

Validation

The stub-based tests route both disposition queries to the same rows, so they pass either way and prove nothing about the windowing. The regression test is therefore real-D1: four merged PRs published 200 days ago plus two recent ones (one auto-closed and reversed, one merged and standing).

Assertion Value
totals.reviewed 6 — lifetime volume unaffected
totals.merged 5 — lifetime volume unaffected
totals.reversed 1
byProject[0].accuracyPct 0 — windowed denominator is 1, not the lifetime 5
totals.accuracyPct 0

Under the old pairing that same fixture published 1 - 1/5 = 80%.

  • 39 changed lines in public-stats.ts: 0 uncovered, 0 partial branches.
  • 52 tests green across public-stats + public-stats-route, including the existing REGRESSION (#7449) test, which still passes — the property it pins is preserved, not replaced.
  • typecheck, ui:openapi:check, git diff --check all clean.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format, for example fix(api): restore profile access checks.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves (e.g. Closes #123) — a linked open issue is required for every contributor PR.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests.
  • API/OpenAPI/MCP behavior is updated and tested where needed.
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks.
  • Visible UI changes include a UI Evidence section below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails. SVG screenshots are not used as review evidence. Review-only screenshots or recordings are not committed to the repository.
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs.

No auth/CORS/session change, and no UI file is touched — the fairness page already renders whatever the block returns, and #9718 shipped the copy explaining a withheld or lowered accuracy. ui:openapi:check confirms the response shape is unchanged: accuracyPct was already nullable, and this narrows what it is computed from, not what it can be.

…r survives in

#9718 gated accuracy on whether a reversal is observable at all, and fixed the
weekly trend's Orb-folded denominator. It did not fix the third asymmetry in
#9676: `merged`/`closed` come from `github_app.pr_public_surface_published`,
the single retention-EXEMPT audit event type, so they are lifetime and never
shrink -- while `reversal_*` rows prune with the rest of audit_events at 90
days. Pairing an immortal denominator with a 90-day numerator makes
`1 - reversed/decided` drift toward 100% as the ledger ages, independent of
real reversal behavior.

Confirmed live after #9718 deployed: byProject still published 100% for all
three repos on 2377/602/508 reviewed with 0 reversals, because the reversal
signal IS observable on that deployment -- the gate passed and the ratio was
still meaningless.

The accuracy denominator is now a second, retention-windowed disposition query
that shares BOTH of the numerator's bounds: own-ledger only, and inside
audit_events' retention window. `reviewed`/`merged`/`closed` keep publishing
lifetime volume, which is measured and correct. That display-vs-denominator
split is the same shape #7449 established for the Orb fold and #9718 carried
into the weekly trend; the #7449 snapshot it supersedes is removed rather than
left dead, with its reasoning folded into the new comment.

Boundary case, stated rather than hidden: a PR published before the window but
reversed inside it contributes to the numerator and not the denominator.
accuracyPct's existing clamp already handles that (it is the same shape as the
reopened-auto-close case its comment describes), and the alternative -- an
unbounded denominator -- is the bug being fixed.

Refs #9676
@loopover-orb

loopover-orb Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-29 08:07:33 UTC

3 files · 1 AI reviewer · 2 blockers · CI green · clean

⏸️ Suggested Action - Manual Review

Review summary
This PR adds a second retention-windowed disposition query (`windowedDispositions`) that supplies the accuracy denominator (merged/closed) so it shares the same 90-day retention bound as the `reversed` numerator, fixing the drift-toward-100% bug described (an immortal `pr_public_surface_published`-sourced denominator paired with a pruned reversal numerator). The wiring is traced correctly end-to-end: both the per-project (`byProject[].accuracyPct`) and global (`totals.accuracyPct`) calculations now use `windowedMerged`/`windowedClosed` instead of lifetime `merged`/`closed`, and the new regression test at `test/unit/public-stats.test.ts` exercises the real boundary (old PRs excluded from the denominator, a recent reversed auto-close, a recent stand-alone merge) with correctly hand-computed expected values (0% not 80%). The one loose end is that the comment directly above the global `accuracyPct:` call site (src/review/public-stats.ts, near the return statement) still says 'See the ownLedgerMerged/ownLedgerClosed snapshot above the Orb fold for why' even though those variables were removed and replaced by `windowedMerged`/`windowedClosed`.

Nits — 6 non-blocking
  • src/review/public-stats.ts, in the `return` object near `accuracyPct: accuracyPct(windowedMerged, windowedClosed, ...)`: the trailing 'Option 1 of fix(review): public-stats accuracyPct denominator includes Orb-fleet merged/closed but numerator (reversed) stays own-ledger-only #7449' comment still references the now-deleted `ownLedgerMerged`/`ownLedgerClosed` snapshot instead of pointing at `windowedMerged`/`windowedClosed` — update it so a future reader isn't sent looking for variables that no longer exist.
  • No test exercises the `windowedByProject.get(...) ?? 0` fallback branch for a project that has lifetime dispositions but zero rows in the windowed query (e.g. a repo whose only PRs are all outside the 90-day window) — worth a case alongside the existing regression test for full branch coverage.
  • The file-header doc comment (`accuracyPct = 1 - reversed / (merged + closed)`) isn't updated to note the denominator is now retention-windowed, not the lifetime merged/closed described a few lines above it.
  • Update the stale 'Option 1 of fix(review): public-stats accuracyPct denominator includes Orb-fleet merged/closed but numerator (reversed) stays own-ledger-only #7449' comment at the global accuracyPct call site to reference windowedMerged/windowedClosed by name.
  • Add a byProject-scoped test case where a project's PRs are entirely outside the retention window, to cover the `windowed?.merged ?? 0` nullish fallback.
  • Diff is mostly generated, vendored, or minified output — Exclude generated, vendored, and minified output and keep the diff focused on substantive changes.

Concerns raised — review before merging

  • No linked issue detected: No closing reference or linked issue number was found in the PR metadata/body. — If this PR is intended to solve an issue, link it explicitly in the PR body.
  • Maintainer requires a linked issue: This repo's maintainer focus manifest requires every PR to reference a tracked issue. — Link the relevant issue (for example Closes #123) before opening the PR.
📋 Copy for AI agents — paste into your coding agent
Fix the following blocker(s) from this PR review:

1. No linked issue detected: No closing reference or linked issue number was found in the PR metadata/body. — If this PR is intended to solve an issue, link it explicitly in the PR body.

2. Maintainer requires a linked issue: This repo's maintainer focus manifest requires every PR to reference a tracked issue. — Link the relevant issue (for example `Closes #123`) before opening the PR.

Decision drivers

  • ❌ Code review — 2 blockers (1 reviewer)
  • ❌ Gate result — Blocking (Repo-configured hard blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ⚠️ Missing No linked issue or no-issue rationale found.
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 (no linked issue context).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 370 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 370 issue(s).
Improvement ✅ Minor risk: low · value: minor
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is registered but has no active allocation in the current snapshot.
  • Public profile languages: not available
  • Official Gittensor activity: 14 PR(s), 370 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Then work through the remaining 4 steps in the Signals table above.
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.

Decision record
  • action: hold · clause: missing_linked_issue
  • config: 39e77796d56eaefe39b5b5f00450a6c637cb19dfbaaaeec5cd29fb86096ec7a2 · pack: oss-anti-slop · ci: passed
  • record: 64dc9f2327218876996c9814d1c70e26f1258b0f49e20035de7edc6f946a01f9 (schema v5, head 03ac9e1)

🟩 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

@JSONbored JSONbored self-assigned this Jul 29, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

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

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.46%. Comparing base (e5cc2cd) to head (03ac9e1).
⚠️ Report is 8 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9768      +/-   ##
==========================================
- Coverage   90.28%   89.46%   -0.82%     
==========================================
  Files         907      907              
  Lines      113363   113369       +6     
  Branches    26892    26894       +2     
==========================================
- Hits       102349   101431     -918     
- Misses       9683    10849    +1166     
+ Partials     1331     1089     -242     
Flag Coverage Δ
backend 94.07% <100.00%> (-1.48%) ⬇️

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

Files with missing lines Coverage Δ
src/review/public-stats.ts 97.60% <100.00%> (+0.12%) ⬆️

... and 3 files with indirect coverage changes

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 29, 2026
… that still exist

Review catch on #9768. The comment above the global accuracyPct call site still
pointed at "the ownLedgerMerged/ownLedgerClosed snapshot above the Orb fold" --
variables this PR removed and replaced with windowedMerged/windowedClosed. A
comment referring a reader to a symbol that no longer exists is worse than none:
it sends them looking for context that was deliberately relocated.

Retargeted at the windowed disposition query, and widened while there. The old
text described ONE of the two bounds the pairing needs (same population, so the
fleet fold cannot inflate it); it now names both, since this PR added the second
(same retention window, so an immortal denominator cannot outlive its numerator).

`ownLedgerReviewed`/`ownLedgerMinutes` are untouched -- different variables, both
still live, feeding the minutes-saved calculation.
@JSONbored

Copy link
Copy Markdown
Owner Author

Good catch — fixed in the latest push.

You're right that the comment above the global accuracyPct: call site still pointed at ownLedgerMerged/ownLedgerClosed, which this PR removes. A comment referring a reader to a symbol that no longer exists is worse than no comment: it sends them hunting for context that was deliberately relocated.

Retargeted at the windowed disposition query, and widened while there. The old text described only one of the two bounds the pairing needs — same population, so the fleet fold can't inflate it. It now names both, since this PR adds the second: same retention window, so an immortal denominator can't outlive its numerator. That second bound is the actual subject of the PR, so the comment was under-describing the code even after the rename was corrected.

For the record, ownLedgerReviewed and ownLedgerMinutes are still present and untouched — different variables, both still live, feeding the minutes-saved calculation. Only the two accuracy-denominator snapshots were removed.

typecheck clean and the 48 public-stats tests still pass.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loopover-ui 03ac9e1 Commit Preview URL

Branch Preview URL
Jul 29 2026, 07:51 AM

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bundle Report

Bundle size has no change ✅

@JSONbored
JSONbored merged commit d8bcb56 into main Jul 29, 2026
11 checks passed
@JSONbored
JSONbored deleted the fix/byproject-accuracy-retention-window branch July 29, 2026 08:08
JSONbored added a commit that referenced this pull request Jul 29, 2026
…s something again (#9775)

After #9718 and #9768 the own-ledger weekly trend is correct and, for recent
weeks, correctly null: the hosted Worker does not execute reviews, so that
ledger is frozen while Orb volume keeps growing. A column of nulls beside a
rising volume column is honest and tells a reader nothing about how the gate
behaves today.

orb_signals already carries everything needed, with no new ingest, no new column
and no new secret: self-host runtimes export gate_verdict/outcome/reversal_flag
per PR, the hosted side validates and stores them, and computeFleetAnalytics
already turns them into the 90-day headline. This buckets the SAME rows weekly.

Two deliberate non-choices, both of which would have reintroduced the bug class
this surface is being corrected for:

  - It is a SEPARATE series, never blended with the own-ledger one. orb_signals
    is keyed by per-instance HMACs of repo/PR; audit_events and orb_pr_outcomes
    use raw owner/repo#number. The populations cannot be joined, so a reversal
    from one can never be attributed to a decided row in the other, and one
    number over a mixed denominator would be exactly the defect #9676 reports.
  - It does NOT use 1 - reversalRate. #8820 established decisionAccuracy as the
    fleet estimand because the reversal formula divides by holds -- deferrals
    that cannot be right or wrong -- and misses mispredictions carrying no
    reversal marker. A weekly series on a different estimand than the headline
    directly above it would make the page disagree with itself.

The weekly fold calls foldInstance rather than reimplementing the confusion
matrix. That function is already exported for the federated bundle with a doc
comment warning that a second copy makes comparisons apples-to-oranges, and the
accounting is subtle: policy_action rows are excluded from scoring but still
count as activity, and a superseded close is disconfirmed exactly like a reopen.

Only registered instances count, matching computeFleetAnalytics' own trust gate
-- the ingest is open, so a stranger's signals must not move a published number
until a human opts them in. There is a test pinning that.

Refs #9676
JSONbored added a commit that referenced this pull request Jul 29, 2026
…ed PR (#9793)

Live after both #9718 and #9768 deployed: byProject still published 100% for all
three repos, on 2377/602/508 reviewed PRs with zero reversals.

Neither earlier fix could have caught it. Both addressed the WINDOW the
denominator covers -- #9718 withheld the ratio where a reversal was unobservable,
#9768 bounded an immortal denominator to its numerator's retention -- and the
defect is WHICH PRs belong in it.

A reversal is by definition a human overturning an engine auto-action;
recordReversalSignals only records one against a PR the engine merged or closed.
The denominator was `github_app.pr_public_surface_published`: every PR that got a
review surface, including the many the engine only commented on, held, or
advised. None of those could produce a reversal, so each one pushes
1 - reversed/decided toward 100% for reasons unrelated to gate quality.

Both surfaces now divide by the distinct PRs this deployment auto-actioned in the
window -- same event types, same outcome filter, same dry-run exclusion that
loadReversalDayRows already applies when anchoring the numerator, so the two
halves of the ratio are finally drawn from one population. The weekly trend had
the same mismatch, masked only because its own-ledger series currently reports
null for recent weeks; fixing one and not the other would have left two
definitions of "decided" on one page.

Volume columns are untouched: reviewed/merged/closed keep publishing lifetime and
fleet-folded counts, which are measured and correct. Only the ratio's denominator
narrows.

#9718's observability probe is REMOVED rather than left beside the thing that
supersedes it. No auto-actions now means decided is 0 and the answer is null by
construction -- a structural guarantee, where the probe was a heuristic that
could disagree with the denominator sitting next to it.

Six fixtures were made faithful rather than the assertion relaxed: each modelled
a decided PR with no auto-action, which is not a reachable state. The clearest is
the revert-PR regression, which recorded a reversal_reverted with no
agent.action.merge -- a PR cannot be reverted unless the engine merged it.

Closes #9792
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant