Skip to content

fix(stats): withhold reversal-grounded accuracy where a reversal cannot be recorded - #9718

Merged
JSONbored merged 1 commit into
mainfrom
fix/fairness-structural-zeros
Jul 29, 2026
Merged

fix(stats): withhold reversal-grounded accuracy where a reversal cannot be recorded#9718
JSONbored merged 1 commit into
mainfrom
fix/fairness-structural-zeros

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

/fairness showed 100% accuracy for every repository and all eight weeks of the trend, while the headline read 94.6%. The 100%s are structural zeros, not measurements.

recordReversalSignals has exactly one call site, inside handlePullRequestWebhookEvent (src/queue/processors.ts:7039), reached only via the github-webhook job — a member of REVIEW_EXECUTION_JOB_TYPES, which a runtime that does not execute reviews acks-and-drops (src/index.ts:61-75). So reversed is pinned at 0 while merged/closed keep growing, and 1 - 0/N renders as a flawless score over a numerator that can never move. The repo already recorded the empirical outcome — src/review/reversal-superseded.ts:3-4: "verified in production: zero reversal events ever."

1. Accuracy is withheld when a reversal is not observable

accuracyPct now returns null unless a reversal is observable on this deployment. A recorded reversal proves it outright — and short-circuits the probe, so no deployment that has ever overturned an auto-action pays a query. Otherwise there must be at least one terminal auto-action (agent.action.close/agent.action.merge) in the retained window for a reversal to attach to.

This is the discipline the surface already applies, extended to the case where the numerator's writer, not just its sample, is absent: a sparse rule's precision is null (PUBLIC_PRECISION_MIN_DECIDED), and gamingFlagsCaught is null below three instances (src/orb/analytics.ts:343). Volume columns are measured directly and still publish.

2. The weekly trend's denominator asymmetry

merged/closed folded in registered-Orb rows (public-accuracy-trend.ts:181-182) while reversed stayed own-ledger-only — the Orb aggregate has no reversal concept — so the denominator grew with every newly registered install while the numerator did not. #7449 fixed exactly this for the lifetime figure (public-stats.ts:463-470) and it was never carried across to the trend. Accuracy now divides the own-ledger pairing; displayed volume still includes the fleet.

3. UI

Not in this PR

ai_consensus_defect and slop_gate_score publish byte-identical decided: 460, confirmed: 287 because the slop backfill copies the AI-consensus corpus's labels verbatim (scripts/backfill-slop-corpus.ts:51-58). That is backfilled row data, not a code path — filtering backfilled rows out of the public precision is a separate change with its own product question. Tracked on #9676.

Refs #9676

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.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally; codecov/patch requires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate.
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

If any required check was skipped, explain why:

  • Coverage measured scoped rather than via a full unsharded test:coverage: vitest run --coverage over public-stats.test.ts, public-accuracy-trend.test.ts and public-stats-route.test.ts, restricted to the two changed modules — 98.4% branch overall, with the single uncovered line (public-stats.ts:564, a pre-existing JSON.parse catch) outside this diff, so codecov/patch (changed lines only) is clean. Every new branch is exercised on both sides: observable/unobservable, the known-reversal short-circuit, the probe's true/false arms, its fail-safe throw path, and the own-ledger-vs-Orb denominator split.
  • UI: npm run ui:test green (643 passed, +3 new). apps/** is outside Codecov's scope by config, so these are correctness tests rather than coverage.
  • test:workers, build:mcp, test:mcp-pack not run locally — this diff touches neither the Workers pool nor the MCP package. Left to CI.

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 behavior changes. ui:openapi:check confirms the OpenAPI artifact is unchanged — accuracyPct was already nullable on the wire, so this narrows when a value is present, not the response shape. That nullability is why the change degrades safely against an older deployed frontend.

UI Evidence

Captured at 1280×800, deviceScaleFactor: 2, dark build. Both states render through the real component against the same captured production payload (/v1/public/stats, fetched live), with only the fields this PR changes differing — so the comparison isolates the rendering change rather than mixing in unrelated live drift. The deployed API predates #9168's basis, so basis is present in both payloads to keep that deploy skew out of the comparison.

State / title Before After
Headline card — basis disclosure Before: 94.6% with no self-report disclosure After: self-report disclosure with sample size and CI
By repository + weekly trend Before: 100% accuracy in every row After: withheld accuracy with an explanation

Notes

This does not restore the reversal signal — it stops the page claiming a measurement it cannot make. Getting real reversal-grounded accuracy back on the hosted surface means either running the reversal writer on that runtime or ingesting reversals from self-host instances the way outcomes already are; both are architectural and tracked on #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 06:42:12 UTC

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

⏸️ Suggested Action - Manual Review

Review summary
This PR withholds reversal-grounded accuracy (returns null) when this deployment cannot possibly have recorded a reversal — a genuine correctness fix, since `reversed` is written only by `recordReversalSignals` off the `github-webhook` job, and a runtime that never runs that job would otherwise render `1 - 0/N` as a false 100%. The trend/public-stats/UI wiring is consistent (loadReversalObservability threaded through both public-stats.ts and public-accuracy-trend.ts, with matching UI notes and tests), and the PR separately fixes a real Orb-fold denominator asymmetry in the weekly trend (own-ledger-only reversed vs fleet-folded merged/closed) that mirrors the #7449 fix already applied to the lifetime figure. The `loadReversalObservability` probe query is a plausible proxy — it can't distinguish 'never ran review-execution' from 'ran but genuinely never auto-acted in the retention window' — but that's a reasonable, disclosed tradeoff rather than a bug.

Nits — 7 non-blocking
  • src/review/public-stats.ts: loadReversalObservability's fallback query re-checks only the retention window for AUTO_ACTION_EVENT_TYPES — a deployment that ran review-execution long enough ago that those events fell out of retention, but still has reversed>0 rows retained, is already covered by the knownReversals short-circuit, but worth a comment confirming that overlap is intentional.
  • apps/loopover-ui/src/components/site/fairness-report-page.tsx:185 embeds a bare `Homepage hero metrics are wrong: decision accuracy pegged at 100%, AI-work-reused renders no value #8820` issue reference inline in JSX prose; consider whether that's meant to survive as a permanent citation or was meant as a dev-facing comment.
  • src/review/public-stats.ts has grown to ~619 lines with this change; consider whether accuracyPct/loadReversalObservability could move to a smaller reversal-specific module as a future cleanup.
  • The PR description promises 'Ac[...]' cut off mid-sentence in what's shown — worth confirming the full description covers the trend denominator fix as clearly as the observability fix for reviewers reading only the summary.
  • Consider a short code comment at the AUTO_ACTION_EVENT_TYPES probe clarifying it's a heuristic (absence of auto-actions in the retention window, not proof review-execution never ran) so a future reader doesn't over-trust it as definitive.
  • Possible screenshot-table issue: pair 1 — Advisory only — verify the screenshot-table images against the stated change before deciding.
  • Possible screenshot-table issue: pair 2 — Advisory only — verify the screenshot-table images against the stated change before deciding.

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, 359 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 359 issue(s).
Improvement ✅ Minor risk: clean · 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), 359 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: 85baab5139e26524a68e094d8bab951703328e3e8f70a1f231c684921599c71f · pack: oss-anti-slop · ci: passed
  • record: 0edd7b091861747116371cefa8a7fab47f73e9bdba084075189656f480eb2261 (schema v5, head 70c8d94)
Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/ desktop before /
before /
after /
after /
diff /
diff /
/ mobile before / (mobile)
before / (mobile)
after / (mobile)
after / (mobile)

Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy · Diff highlights exactly what changed.

Scroll preview
Route Before (production) After (this PR's preview)
/ before / (scroll)
before / (scroll)
after / (scroll)
after / (scroll)

A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show.

🟩 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.

@JSONbored

Copy link
Copy Markdown
Owner Author

Corrected the UI Evidence: the before-fairness-* screenshots were initially captured while this branch's component was already in the tree, so the headline shot showed the new self-report disclosure and the caption misdescribed it. Both before-state images have been recaptured against origin/main's fairness-report-page.tsx and re-pushed to the same URLs — the table above now genuinely shows main vs this branch. The after-state images were always correct.

@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 70c8d94 Commit Preview URL

Branch Preview URL
Jul 29 2026, 06:24 AM

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 1.43kB (0.02%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
loopover-ui 7.8MB 1.43kB (0.02%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-CrbVyVBZ.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-B22Ucz-V.js (New) 913.91kB 913.91kB 100.0% 🚀
assets/docs.fumadocs-spike-api-reference-C8Arqgls.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-DuzPM5Xp.js (New) 201.7kB 201.7kB 100.0% 🚀
assets/modal-BKU-Nz_B.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-Ba5uXB1F.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/maintainer-panel-CpIJi97S.js (New) 78.99kB 78.99kB 100.0% 🚀
assets/routes-CrncDfor.js (New) 35.96kB 35.96kB 100.0% 🚀
assets/owner-panel-PxVppMiw.js (New) 27.92kB 27.92kB 100.0% 🚀
assets/app-BLyCXDbN.js (New) 25.78kB 25.78kB 100.0% 🚀
assets/ui-vendor-BLBUaN_u.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/miner-panel-bq-q9qWI.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-Jj990xEs.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/api._op-CBhDIOxN.js (New) 17.57kB 17.57kB 100.0% 🚀
assets/self-hosting-docs-audit-COW0t4EB.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-BILNBDvD.js (New) 15.52kB 15.52kB 100.0% 🚀
assets/playground-panel-Bx7rbhxm.js (New) 14.42kB 14.42kB 100.0% 🚀
assets/fairness-Dy2D4T_I.js (New) 12.16kB 12.16kB 100.0% 🚀
assets/app.audit-C1iDIAon.js (New) 10.08kB 10.08kB 100.0% 🚀
assets/app.config-generator-CchGnXNT.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-BKHeIbVX.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-B94gBQA0.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-DUXfljZN.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-DBZ8srjl.js (New) 6.65kB 6.65kB 100.0% 🚀
assets/maintainer-workflow-Beo3dZE5.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel-dMqvXyW0.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-BpHfa8U3.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-DxKhv7Jz.js (New) 6.01kB 6.01kB 100.0% 🚀
assets/docs.index-y_14O1tp.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-PWbM0X6V.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-CgOyxQYA.js (New) 2.7kB 2.7kB 100.0% 🚀
assets/api-Ba6El6V3.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-DTUu8r0C.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-x32WDCwb.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-B51ZShex.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-DgRcPDjA.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-BKYV_sRC.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-CgTecTWO.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-Dw2jw6n_.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer--sf0EZVq.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-CJj0PfWn.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-Bl2jrGmI.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-gHroAyj3.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-BNaTs5G2.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-BZABiuUX.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-BkL0Hke8.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-DcXTb25T.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-CLtuILSk.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-UkpfS1-L.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-DHwQjHWn.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-BGW-EjWg.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-Ct-DZOZ4.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-B5pmkTFp.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-CSwQZ1B_.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-BBmRLKSq.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-CyuuEF49.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-CSEzt7_Y.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-UYHqvDXZ.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-D6doxcEy.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-B5xhXRNQ.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-CLmiftRA.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-Be7dtln8.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-CGSpsB5i.js (Deleted) -913.91kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-DTLtPPeF.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-iGEdSZeI.js (Deleted) -201.7kB 0 bytes -100.0% 🗑️
assets/modal-*.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-CVCtmw1n.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-BjOTlahS.js (Deleted) -78.99kB 0 bytes -100.0% 🗑️
assets/routes-DA54I6Le.js (Deleted) -35.96kB 0 bytes -100.0% 🗑️
assets/owner-panel-BxuMiuCn.js (Deleted) -27.92kB 0 bytes -100.0% 🗑️
assets/app-AU--k-vI.js (Deleted) -25.78kB 0 bytes -100.0% 🗑️
assets/ui-vendor-BQS1Zftn.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/miner-panel-CqM7MPS5.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-CeYgmORT.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/api._op-BFQWfcn9.js (Deleted) -17.57kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-CvXT8jki.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-DgmQKj7f.js (Deleted) -15.52kB 0 bytes -100.0% 🗑️
assets/playground-panel-Dj7muM7P.js (Deleted) -14.42kB 0 bytes -100.0% 🗑️
assets/fairness-dmwQ4n4o.js (Deleted) -10.73kB 0 bytes -100.0% 🗑️
assets/app.audit-DpZEfpKc.js (Deleted) -10.08kB 0 bytes -100.0% 🗑️
assets/app.config-generator-BlMIdWhd.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-Dst5ojPo.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-Dzmljb2C.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-Dpk_gj0x.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-B8Gi4y1P.js (Deleted) -6.65kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-C0crBeos.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-BXuwCicO.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-CCza5CtT.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-tyQcMghf.js (Deleted) -6.01kB 0 bytes -100.0% 🗑️
assets/docs.index-Cj6CZCAs.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-Be00UFYE.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-DI0mpBC2.js (Deleted) -2.7kB 0 bytes -100.0% 🗑️
assets/api-De8cjXIF.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-DdqdagYF.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-CEn13uEB.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-CCXUmRT5.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-CsSviwlD.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-DAdwTweL.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-BqPvSDz1.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-DUOq-S_7.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-DNYF1pQP.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-CHOzQ_gj.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-ByWQs0vO.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-DNsmpRJM.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-Cz0cCEOv.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-BM904ls-.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-dAjfDE0F.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-rRCgHu8S.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-lxONSK7S.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-DPCo-rju.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-DZtAjewF.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-9KzIkFPF.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-x6aMbwhE.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-D_5FQ8RJ.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-Cf1utcRQ.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history--DHChrMH.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-ByGHetBo.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-DJ30kMKk.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-G_yqUkAE.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-BERm0GS4.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-CrXj_H86.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-Ssscojx0.js (Deleted) -174 bytes 0 bytes -100.0% 🗑️

@JSONbored JSONbored self-assigned this Jul 29, 2026
@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 (5ecb771) to head (70c8d94).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9718      +/-   ##
==========================================
- Coverage   90.27%   89.46%   -0.82%     
==========================================
  Files         904      904              
  Lines      113177   113195      +18     
  Branches    26840    26849       +9     
==========================================
- Hits       102171   101265     -906     
- Misses       9676    10842    +1166     
+ Partials     1330     1088     -242     
Flag Coverage Δ
backend 94.06% <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.47% <100.00%> (+0.20%) ⬆️
src/services/public-accuracy-trend.ts 100.00% <100.00%> (ø)

... and 3 files with indirect coverage changes

…ot be recorded

The fairness page showed 100% accuracy for every repository and all eight
weeks of the trend while the headline read 94.6%. Those 100%s are structural
zeros, not measurements.

`recordReversalSignals` has one call site, inside `handlePullRequestWebhookEvent`,
reached only via the `github-webhook` job — a member of
REVIEW_EXECUTION_JOB_TYPES, which a runtime that does not execute reviews
acks-and-drops. So `reversed` is pinned at 0 while merged/closed keep growing,
and `1 - 0/N` renders as a flawless score over a numerator that can never move.
The repo already recorded the outcome: reversal-superseded.ts notes "verified in
production: zero reversal events ever".

`accuracyPct` now returns null unless a reversal is OBSERVABLE on this
deployment — a recorded reversal proves it outright (and short-circuits the
probe), otherwise there must be at least one terminal auto-action in the
retained window for a reversal to attach to. This is the discipline the surface
already applies elsewhere, extended to the case where the numerator's WRITER,
not just its sample, is absent: a sparse rule's precision is null, and
gamingFlagsCaught is null below three instances.

Also fixes the weekly trend's denominator asymmetry: `merged`/`closed` folded in
registered-Orb rows while `reversed` stayed own-ledger-only, so the denominator
grew with every new install while the numerator did not — trending every week
toward 100% independent of real reversal behavior. #7449 fixed exactly this for
the lifetime figure and it was never carried across. Accuracy now divides the
own-ledger pairing; the displayed volume still includes the fleet.

UI: the withheld cells say why rather than leaving a bare em dash next to
healthy volume, and the headline now discloses `basis` — #9168 computes
"single_instance_self_report" precisely so one operator's self-reported number
is not read as fleet corroboration, and the page dropped the field entirely,
along with the sample size and confidence interval. The methodology note also
described the reversal formula while the headline rendered decisionAccuracy;
it now explains both and why they differ.

Refs #9676
@JSONbored
JSONbored force-pushed the fix/fairness-structural-zeros branch from 3e198e7 to 70c8d94 Compare July 29, 2026 06:21
@JSONbored
JSONbored merged commit 73e30da into main Jul 29, 2026
11 checks passed
@JSONbored
JSONbored deleted the fix/fairness-structural-zeros branch July 29, 2026 06:46
JSONbored added a commit that referenced this pull request Jul 29, 2026
…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
JSONbored added a commit that referenced this pull request Jul 29, 2026
…r survives in (#9768)

* fix(stats): bound the accuracy denominator to the window its numerator 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

* docs(stats): retarget the global accuracyPct comment at the variables 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 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