Skip to content

metrics(ledger): re-evaluation counts and per-author-class review-parity rollups - #9877

Open
JSONbored wants to merge 5 commits into
mainfrom
feat/reevaluation-parity-rollups-9743
Open

metrics(ledger): re-evaluation counts and per-author-class review-parity rollups#9877
JSONbored wants to merge 5 commits into
mainfrom
feat/reevaluation-parity-rollups-9743

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Closes #9743. Closes #9744. Completes epic #9741.

Problem

Two questions this deployment could only answer with an ad-hoc query: how often is a verdict re-evaluated, and why, and does a PR face the same scrutiny regardless of who wrote it. #9742 made every repeat verdict declare a reason; this counts them, and puts the author-class comparison beside it.

Everything is computed from the anchored ledger — which forced one schema change

decision_records and nothing else, so an outsider holding the export can recompute every published figure. That constraint is load-bearing rather than stylistic, and it is why findingsCount is now recorded on the decision record (schema v6, migration 0205): findings existed only in ai_review_cache, which is keyed for reuse rather than anchored and is pruned independently. A fairness number counted out of a cache is unverifiable, and would have gone quietly wrong the first time the cache turned over.

Author class is GitHub's, not ours

pull_requests.author_association — mechanical, so a permissions change upstream changes the class with no code change here, which is what #9743 asked for. That predicate was already spelled out at four independent call sites (the mention-command gate, the settings preview, the advisory rules, and local-branch's owner check — one lower-cased, one under a different name). It now lives once in src/github/author-association.ts and they all read it.

The definitions that are easy to get wrong

Stated beside the code, in the verifier walkthrough, and on the page itself:

  • verdicts counts evaluations, not pull requests. A repeat evaluation of one head SHA is its own ledger row — which is exactly what reviewsPerPr divides out.
  • findingsPerPr excludes verdicts that recorded no count, rather than averaging them in as zero, and publishes findingsBasis — the coverage the mean was earned at. A mean over 3 of 400 verdicts is not the same claim as a mean over 400.
  • An unrecorded association is its own unknown bucket, never folded into either side. Folding it would bias the exact comparison being published.
  • A rate over an empty window is null, never 0. "Nothing was held" and "nothing was measured" are different claims.
  • shareOfVerdictsPct is a share of all verdicts, not of re-evaluations — the latter always sums to 100% and says nothing about how often re-evaluation happens at all.

/fairness (#9744)

Both series render with their methodology where the numbers are. The zero states carry the weight: an empty window renders as a measured zero with its dates, and "no re-evaluations recorded" is a separately-worded state from "no verdicts recorded" because they are different facts.

The payload shape went into the contract's zod schema, so the UI type is derived. Adding the block made both existing UI fixtures fail to compile — which is precisely the guard #9282/#9521 put there, doing its job.

Validation

  • 24 unit tests on the rollups module: 100% statements, 100% branches. Every changed line and branch across the diff is covered, branch-counted, with no v8 ignore suppressions.
  • 1,056 UI tests pass; ui:typecheck, ui:build, ui:lint (0 errors) clean.
  • db:migrations:check (contiguous 0001..0205), db:schema-drift:check, ui:openapi:check, contract:api-schemas:check, ui-derived-types:check, docs:drift-check, dead-exports:check, import-specifiers:check all clean.
  • The schema bump to v6 surfaced four tests pinning v5; each was updated deliberately, not silenced.

@loopover-orb

loopover-orb Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Caution

🛑 LoopOver review result - fixes required

Review updated: 2026-07-29 20:07:34 UTC

25 files · 1 AI reviewer · no blockers · CI failing · unstable

🛑 Suggested Action - Manual Review

Review summary
This PR closes #9743/#9744 by adding a `findingsCount` column to the anchored `decision_records` ledger (schema v6, migration 0205), building pure/testable rollup functions for re-evaluation rates and per-author-class parity, wiring them through `/v1/public/stats`, the OpenAPI contract, and the fairness report UI, and consolidating four duplicated `author_association` predicates into a single `src/github/author-association.ts` module. The migration is D1-safe (plain `ADD COLUMN` + `CREATE INDEX IF NOT EXISTS`, no temp objects/PRAGMA/transactions), the INSERT statement's placeholder count matches its 13 bound values, and the pure rollup functions are extensively unit-tested including the null-vs-zero and empty-window edge cases the module's own comments call out. CI shows `validate-tests` and `validate` as FAILED with no detail provided, so I can't verify why from what's given here — worth checking before merge.

Nits — 6 non-blocking
  • `ratePct`/`mean` and the 7-day default window in src/review/review-parity-rollups.ts:209-211 use bare numeric literals (`7`, `86_400_000`); a named constant would match the style used elsewhere in this file for `AUTHOR_CLASSES`.
  • `buildReviewParityRollups`'s `reevaluated` filter + tally block (src/review/review-parity-rollups.ts:181) nests to depth 5 — could be flattened by extracting the tally loop into its own small helper.
  • `src/rules/advisory.ts`, `src/signals/local-branch.ts`, and `src/api/routes.ts` are already large files that this PR adds a few lines to; not this PR's fault, but worth flagging for future splitting.
  • Confirm the two failing CI checks (`validate-tests`, `validate`) before merge, since no failure detail is available here to rule out a real break.
  • Consider whether any other DecisionRecord-construction call sites outside this diff need an explicit `findingsCount` given the type now requires it as a non-optional field on `DecisionRecord` itself (buildDecisionRecord defaults it, but a caller constructing the type directly would not).
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

CI checks failing

  • validate
  • validate-tests
  • validate-code

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 #9743, #9744
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 (2 linked issues).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 344 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 344 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Linked issue satisfaction

Addressed
The diff implements weekly re-evaluation counts by reason code as a share of verdicts, per-author-class (mechanically derived via GitHub's author_association) parity rollups (reviews/PR, findings/PR, close/hold rate) with per-repo granularity, adds the schema/migration to make findings ledger-derivable, documents definitions beside the code and in a verifier walkthrough, and exposes it all on the

Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 14 PR(s), 344 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
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: success
  • config: 6b26c60e180d9d677ec3099b7f5d9589f2f994c36cad0638eb7243a931652590 · pack: oss-anti-slop · ci: failed
  • record: 762e7bee5f1bbbe3125369e8070c5c2232486f8ebac9f009128d42efa6639a37 (schema v5, head 059394c)
Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/ desktop before /
before /
after /
after /
/ 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.

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

superagent-security Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

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

@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 059394c Commit Preview URL

Branch Preview URL
Jul 29 2026, 07:44 PM

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bundle Report

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

Detailed changes
Bundle name Size Change
loopover-ui 7.93MB 18.49kB (0.23%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-mnAmoSrk.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-BRMi9x-r.js (New) 933.94kB 933.94kB 100.0% 🚀
openapi.json 5.62kB 755.56kB 0.75%
assets/docs.fumadocs-spike-api-reference-BsdiPnEP.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-C_YwE1ly.js (New) 201.7kB 201.7kB 100.0% 🚀
assets/modal-Dkd5shal.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-C0G5-8i9.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/styles-B726qH-1.css (New) 130.0kB 130.0kB 100.0% 🚀
assets/maintainer-panel-CM2i3DWf.js (New) 78.96kB 78.96kB 100.0% 🚀
assets/verify-this-review-W-ncAjyU.js (New) 56.7kB 56.7kB 100.0% 🚀
assets/routes-CyCKiA4_.js (New) 36.4kB 36.4kB 100.0% 🚀
assets/owner-panel-FOJJ31e_.js (New) 28.18kB 28.18kB 100.0% 🚀
assets/app-Dy2OaC6z.js (New) 25.82kB 25.82kB 100.0% 🚀
assets/ui-vendor-cm7P9La3.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/miner-panel-DBOf6JdM.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-DgUVhrDY.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/fairness-MiRiS02P.js (New) 19.18kB 19.18kB 100.0% 🚀
assets/api._op-DQ3Ij3Rw.js (New) 17.54kB 17.54kB 100.0% 🚀
assets/self-hosting-docs-audit-DnES1CGN.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-D3ifTwrC.js (New) 15.63kB 15.63kB 100.0% 🚀
assets/playground-panel-CiXtKb_u.js (New) 14.42kB 14.42kB 100.0% 🚀
assets/app.audit-lv8WYvwp.js (New) 10.22kB 10.22kB 100.0% 🚀
assets/app.config-generator-FcuNusV6.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-BE79X3x1.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-CW_KeDL6.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-QJI0Yx-H.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-yn0EHpBs.js (New) 6.74kB 6.74kB 100.0% 🚀
assets/maintainer-workflow-6WC_3X9x.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel-C8n6tARD.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-Doa1gMxy.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-BJEtjHB_.js (New) 6.06kB 6.06kB 100.0% 🚀
assets/docs.index-rNWkguxa.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-C_bYtmjo.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-DAi2_tXi.js (New) 2.93kB 2.93kB 100.0% 🚀
assets/api-SQxLsPCZ.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-Dla9WmD2.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-DLPXFPfs.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-CUQapb5W.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-D0lk9LQt.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-CbGbfKek.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-NmfX0Ba-.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-MDIgNn2D.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-BKmREr5Y.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-ChpRoyOB.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-DcCu2doX.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-B11P_hgx.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-BvNs2bPJ.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-ChlVRyyR.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-CwSLqcl-.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-DdDu5Xzg.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-BnmwZ86W.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-D9jkQYM2.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-NFDn74Gt.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-x-4_d4I-.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-ZXlXtw9l.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-DiUvGNFk.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-BCIa6Qdb.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-rcsqNXkP.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-CY_owFq1.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-BRNAEYWA.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-DFm3CJFq.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-DpzTgrQb.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-CC1VtMgg.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-Cb76LImf.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-D41R-T27.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-KsQ4HaYp.js (Deleted) -932.08kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-C4N4waNy.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-2lC6DiYb.js (Deleted) -201.7kB 0 bytes -100.0% 🗑️
assets/modal-CHtpkJft.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-Bt4YtpQ3.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/styles-DJFWcZy-.css (Deleted) -129.97kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-BlBWJPyV.js (Deleted) -78.96kB 0 bytes -100.0% 🗑️
assets/verify-this-review-Ck0f0_EJ.js (Deleted) -50.91kB 0 bytes -100.0% 🗑️
assets/routes-CdgPIiwD.js (Deleted) -36.4kB 0 bytes -100.0% 🗑️
assets/owner-panel-CYUisJuf.js (Deleted) -28.18kB 0 bytes -100.0% 🗑️
assets/app-DNngSU6Q.js (Deleted) -25.82kB 0 bytes -100.0% 🗑️
assets/ui-vendor-CR2nADRA.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/miner-panel-DLwwuy_N.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-sSAE0Yx_.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/api._op-CoLRQU8k.js (Deleted) -17.54kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-DNrqZHNN.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-peft3wcA.js (Deleted) -15.63kB 0 bytes -100.0% 🗑️
assets/playground-panel-PU3NYke4.js (Deleted) -14.42kB 0 bytes -100.0% 🗑️
assets/fairness-BWgcmo7k.js (Deleted) -13.98kB 0 bytes -100.0% 🗑️
assets/app.audit-Dkh3Yazl.js (Deleted) -10.22kB 0 bytes -100.0% 🗑️
assets/app.config-generator-DbggvvSW.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-CXSZzLmI.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-BpWKfqxQ.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-DV_-Id6N.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-Bode-dwD.js (Deleted) -6.74kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-DNfy_YfM.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-B_Zg9qeS.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-Dqn9AzON.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-BIqKFbvi.js (Deleted) -6.06kB 0 bytes -100.0% 🗑️
assets/docs.index-BHb0IhbG.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-CQT2uHxh.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-qAiOT2qu.js (Deleted) -2.93kB 0 bytes -100.0% 🗑️
assets/api-Cb0Rx4f9.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-Bdlrzgq_.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-DFBbmNy7.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-B8-aXEvJ.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-51LZa_y5.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-B9e650Bs.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-CfDdeNaY.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-BhUULYTt.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-BxjJPAe0.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-kwP-3r4_.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-RQvVEVdF.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-mFGRLVQD.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-zcw-Wl3B.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-DaEzua5u.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-BLg9e0B5.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-BBxXhAIL.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-BvsOy7nV.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-C2APo0Tw.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-2CT9Jvtw.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-B8bWpvvI.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-ChIHEQO_.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-DcfQhF8P.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-DmNwz_Q2.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-Ca4ByVVG.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-CQ_NkbeU.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-E_1n_O0s.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-qjwRykgm.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-dR-echoq.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-DtcukxQF.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-kXpgX4hs.js (Deleted) -174 bytes 0 bytes -100.0% 🗑️

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
25584 2 25582 21
View the top 2 failed test(s) by shortest run time
test/unit/migration-collisions.test.ts > KNOWN_MIGRATION_DUPLICATES (#2550) > stays bidirectionally in step with the real migrations/ directory (#9653) > every migration number with more than one file on disk is a KNOWN_MIGRATION_DUPLICATES key
Stack Traces | 0.0126s run time
AssertionError: expected false to be true // Object.is equality

- Expected
+ Received

- true
+ false

 ❯ test/unit/migration-collisions.test.ts:119:93
test/unit/check-migrations-script.test.ts > check-migrations script > reports every grandfathered duplicate migration number in the success summary
Stack Traces | 0.785s run time
Error: Command failed: .../loopover/node_modules/.bin/tsx scripts/check-migrations.ts
check-migrations: duplicate migration number 0205: "0205_decision_record_findings_count.sql", "0205_visual_capture_retry_pending_at.sql". Two PRs grabbed the same number — renumber the newest to the next free number (0206).

 ❯ test/unit/check-migrations-script.test.ts:38:20

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { status: 1, signal: null, output: [ null, '', 'check-migrations: duplicate migration number 0205: "0205_decision_record_findings_count.sql", "0205_visual_capture_retry_pending_at.sql". Two PRs grabbed the same number — renumber the newest to the next free number (0206).\n' ], pid: 21009, stdout: '', stderr: 'check-migrations: duplicate migration number 0205: "0205_decision_record_findings_count.sql", "0205_visual_capture_retry_pending_at.sql". Two PRs grabbed the same number — renumber the newest to the next free number (0206).\n' }

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

…ity rollups (#9743)

Two questions this deployment could only answer with an ad-hoc query now have
standing numbers on /v1/public/stats: how often a verdict is re-evaluated and why,
and whether a PR faces the same scrutiny regardless of who wrote it.

Everything is computed from decision_records -- the anchored ledger -- and nothing
else, so an outsider holding the export can recompute every figure. That constraint
is why findingsCount is now recorded ON the decision record (v6, migration 0205):
findings existed only in ai_review_cache, which is keyed for reuse rather than
anchored and is pruned independently, so a fairness number counted from it would
have been unverifiable and quietly wrong once the cache turned over.

Author class is GitHub's own author_association, not a list this project maintains.
That predicate was spelled out at four independent call sites -- one lower-cased,
one under a different name -- so it now lives once in github/author-association.ts
and they all read it.

The definitions that are easy to get wrong are stated beside the code and in the
verifier walkthrough: verdicts counts EVALUATIONS not PRs; findingsPerPr excludes
verdicts that recorded no count rather than averaging them in as zero, and publishes
the coverage it was earned at; an unrecorded association is its own `unknown` bucket
rather than folded into either side, because folding it would bias the exact
comparison being published; and a rate over an empty window is null, never 0.
Both series #9743 computes now render on /fairness, with the methodology stated
where the numbers are rather than only in the docs.

The zero states are the point. A window with no verdicts renders as a MEASURED zero
with its own dates -- "not missing data" in as many words -- and "no re-evaluations
recorded" is a separate, separately-worded state from "no verdicts recorded",
because they are different facts and a reader cannot tell them apart otherwise.
A mean with no basis reads as insufficient data, never as 0, and every published
mean carries the count it was earned at beside it.

The payload shape is added to the contract's zod schema, so the UI type is DERIVED
rather than hand-kept: adding the block made both existing fixtures fail to compile,
which is exactly the guard #9282/#9521 put there.
…ine package

The gate-advisory TWIN carried a FIFTH copy of the OWNER/MEMBER/COLLABORATOR
predicate, and the engine is standalone -- it cannot import out of src/. So the
definition lives in @loopover/engine and src/github/author-association.ts
re-exports it, the same shape settings/pr-type-label.ts already uses for exactly
this reason. Both twins now read one definition.
persistDecisionRecord bound record.findingsCount raw. A caller that assembles a
DecisionRecord literal rather than going through buildDecisionRecord leaves it
undefined, D1 refuses undefined as a bind, and the insert threw straight into the
outer catch -- so the row silently never appeared and the ledger simply had fewer
decisions in it, with nothing surfaced anywhere.

Caught by proof-summary's own fixtures, which build records that way. The bind
coalesces to null now (the column is nullable and undefined is never a valid bind),
with a regression test that fails if the coalesce is removed.
… ENGINE's own suite

Same cause as the sibling fix on #9847. packages/loopover-engine/src/** is credited
by two Codecov uploads, and the "engine" flag is fed by the package's own node:test
suite -- so a module that only the root vitest suite exercises reads as uncovered
there. author-association.ts moved INTO the engine in this PR, which makes every one
of its lines new to that flag.

The predicate is now behaviour-tested in the engine suite, including through
buildPullRequestAdvisory, since the gate-advisory twin is one of its callers and is
where the fifth duplicate copy lived.
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.

ui(fairness): publish re-evaluation and review-parity series metrics(ledger): re-evaluation counts and per-author-class review-parity rollups

1 participant