From baaedcd6ba083d9cfab010eb632fa16446d21ebb Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Wed, 29 Jul 2026 00:26:32 -0700 Subject: [PATCH 1/2] 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 --- src/review/public-stats.ts | 49 +++++++++++++++++++++++++++------- test/unit/public-stats.test.ts | 36 +++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/review/public-stats.ts b/src/review/public-stats.ts index f5866ba45b..3c20055f47 100644 --- a/src/review/public-stats.ts +++ b/src/review/public-stats.ts @@ -356,9 +356,10 @@ export async function getPublicStats( // The own-ledger side needs at least one allowlisted project to query; an empty allowlist skips these three // queries entirely (own-ledger totals stay zero) but still lets the Orb aggregate below run. const inList = projects.map(() => "?").join(", "); - const [dispositions, reversalRows, weeklyRows, effortRows] = projects.length === 0 + const [dispositions, windowedDispositions, reversalRows, weeklyRows, effortRows] = projects.length === 0 ? await Promise.all([ Promise.resolve([]), + Promise.resolve<{ project: string; merged: number; closed: number }[]>([]), Promise.resolve<{ project: string; reversed: number }[]>([]), Promise.resolve<{ reviewed: number; merged: number }[]>([]), Promise.resolve<{ totalMinutes: number | null }[]>([]), @@ -377,6 +378,25 @@ export async function getPublicStats( GROUP BY ev.repo`, ...projects, ), + // The ACCURACY denominator, bounded to the same window its numerator can survive in. `reviewed`/ + // `merged`/`closed` above are lifetime: `github_app.pr_public_surface_published` is the single + // retention-EXEMPT audit event type (src/db/retention.ts's DURABLE_AUDIT_EVENT_TYPES), so those + // counts never shrink. `reversal_*` rows are pruned 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 -- the same shape #7449 fixed for the + // Orb-folded denominator, and confirmed live (2377/602/508 reviewed, 0 reversals, 100% each). + safeAll<{ project: string; merged: number; closed: number }>( + env, + `SELECT ev.repo AS project, + SUM(CASE WHEN pr.merged_at IS NOT NULL THEN 1 ELSE 0 END) AS merged, + SUM(CASE WHEN pr.state = 'closed' AND pr.merged_at IS NULL THEN 1 ELSE 0 END) AS closed + FROM (SELECT DISTINCT repo, number FROM (${PUBLISHED_PR_KEYS}) WHERE created_at >= ?) ev + LEFT JOIN pull_requests pr ON pr.repo_full_name = ev.repo AND pr.number = ev.number + WHERE LOWER(ev.repo) IN (${inList}) + GROUP BY ev.repo`, + retentionCutoffIsoForTable("audit_events"), + ...projects, + ), safeAll<{ project: string; reversed: number }>( env, // A "reversal" = a human overturning a terminal engine auto-action, already detected and recorded by @@ -466,6 +486,10 @@ export async function getPublicStats( // question about the same deployment, rather than one of them silently disagreeing with the others. const totalReversals = [...reversedByProject.values()].reduce((sum, n) => sum + n, 0); const reversalObservable = await loadReversalObservability(env, totalReversals); + // project -> the retention-windowed merged/closed pairing for `reversed`. See the query's own comment. + const windowedByProject = new Map(windowedDispositions.map((row) => [String(row.project).toLowerCase(), row])); + let windowedMerged = 0; + let windowedClosed = 0; const byProject = dispositions .map((d) => { const merged = d.merged ?? 0; @@ -474,6 +498,11 @@ export async function getPublicStats( const reversed = reversedByProject.get(String(d.project).toLowerCase()) ?? 0; const reviewed = merged + closed + inReview; + const windowed = windowedByProject.get(String(d.project).toLowerCase()); + const windowedRepoMerged = windowed?.merged ?? 0; + const windowedRepoClosed = windowed?.closed ?? 0; + windowedMerged += windowedRepoMerged; + windowedClosed += windowedRepoClosed; totals.handled += reviewed; totals.merged += merged; totals.closed += closed; @@ -485,7 +514,7 @@ export async function getPublicStats( reviewed, merged, closed, - accuracyPct: accuracyPct(merged, closed, reversed, reversalObservable), + accuracyPct: accuracyPct(windowedRepoMerged, windowedRepoClosed, reversed, reversalObservable), }; }) .filter((r) => r.reviewed > 0) @@ -501,13 +530,13 @@ export async function getPublicStats( // Snapshot before Orb merge: effort SQL only covers allowlisted own-ledger publishes, while `reviewed` // below includes Orb fleet outcomes folded into totals.merged/closed. const ownLedgerReviewed = reviewedOf(totals); - // #7449: also snapshot the pre-fold own-ledger merged/closed. totals.reversed stays own-ledger-only (the Orb - // aggregate has no reversal concept), so the published global accuracyPct below is computed from THESE, not the - // fleet-folded totals.merged/closed -- otherwise the denominator would grow with every newly registered install - // while the numerator stayed own-ledger-scoped, trending the percentage toward 100 independent of real reversal - // behavior. The fleet fold still (correctly) inflates reviewed/handled/minutesSaved, which have no such pairing. - const ownLedgerMerged = totals.merged; - const ownLedgerClosed = totals.closed; + // #7449 (superseded, same reasoning): the published global accuracyPct is NOT computed from + // totals.merged/closed. Those are lifetime AND fleet-folded, while totals.reversed is own-ledger-only and + // 90-day-pruned, so pairing them lets the denominator grow -- with every newly registered install, and with + // every additional day of immortal publish events -- while the numerator cannot, trending the percentage + // toward 100 independent of real reversal behavior. windowedMerged/windowedClosed (accumulated above) is the + // pairing that shares BOTH of the numerator's bounds: own-ledger only, and inside audit_events' retention + // window. The fleet fold still correctly inflates reviewed/handled/minutesSaved, which have no such pairing. // Fleet accuracy (bugfix, #fairness-analytics): independent of the own-ledger allowlist above, so it's fetched // unconditionally alongside the Orb global fold, matching that fold's own unscoped-regardless-of-allowlist // behavior (see the "skips the own-ledger queries but still queries the Orb aggregate" test). @@ -613,7 +642,7 @@ export async function getPublicStats( // Option 1 of #7449: compute the global accuracy from the OWN-LEDGER merged/closed snapshot (not the // fleet-folded totals.merged/closed), so its numerator (own-ledger reversed) and denominator are drawn // from the same population. See the ownLedgerMerged/ownLedgerClosed snapshot above the Orb fold for why. - accuracyPct: accuracyPct(ownLedgerMerged, ownLedgerClosed, totals.reversed, reversalObservable), + accuracyPct: accuracyPct(windowedMerged, windowedClosed, totals.reversed, reversalObservable), minutesSaved, }, weekly: { reviewed: w.reviewed ?? 0, merged: w.merged ?? 0 }, diff --git a/test/unit/public-stats.test.ts b/test/unit/public-stats.test.ts index d721bcec63..053ad1f494 100644 --- a/test/unit/public-stats.test.ts +++ b/test/unit/public-stats.test.ts @@ -737,6 +737,42 @@ describe("getPublicStats — live aggregate over the review ledger", () => { expect(out.byProject.map((row) => row.accuracyPct)).toEqual([null]); }); + it("REGRESSION: the accuracy denominator is bounded to audit_events' retention window, not lifetime (real D1)", async () => { + // `github_app.pr_public_surface_published` is the ONE retention-exempt event type, so reviewed/merged/ + // closed are immortal, while `reversal_*` rows prune at 90 days. Pairing them made 1 - reversed/decided + // drift toward 100% as the ledger aged -- live: 2377/602/508 reviewed, 0 reversals, 100% each. + const env = createTestEnv({ LOOPOVER_PUBLIC_STATS_REPOS: "JSONbored/loopover" }); + const old = new Date(NOW - 200 * 86_400_000).toISOString(); // outside the 90-day window + const recent = new Date(NOW - 5 * 86_400_000).toISOString(); + await upsertRepositoryFromGitHub(env, { name: "loopover", full_name: "JSONbored/loopover", private: false, owner: { login: "JSONbored" } }, 1); + + // Four ancient merged PRs -- they keep counting toward `reviewed`, but must NOT pad the accuracy + // denominator, because a reversal of any of them would long since have been pruned. + for (const number of [1, 2, 3, 4]) { + await upsertPullRequestFromGitHub(env, "JSONbored/loopover", { number, title: `old ${number}`, state: "closed", merged_at: old, user: { login: "a" }, head: { sha: `s${number}` }, labels: [] }); + await recordAuditEvent(env, { eventType: "github_app.pr_public_surface_published", targetKey: `JSONbored/loopover#${number}`, outcome: "completed", createdAt: old }); + } + // One recent auto-closed PR, reversed by a human. + await upsertPullRequestFromGitHub(env, "JSONbored/loopover", { number: 5, title: "recent", state: "open", user: { login: "b" }, head: { sha: "s5" }, labels: [] }); + await recordAuditEvent(env, { eventType: "github_app.pr_public_surface_published", targetKey: "JSONbored/loopover#5", outcome: "completed", createdAt: recent }); + await recordAuditEvent(env, { eventType: "agent.action.close", targetKey: "JSONbored/loopover#5", outcome: "completed", createdAt: recent }); + await recordAuditEvent(env, { eventType: "reversal_reopened", targetKey: "JSONbored/loopover#5", outcome: "completed", createdAt: recent }); + // ...and one recent merged PR that stands, so the windowed denominator is 1 merged + 0 closed... plus + // PR#5 which is currently `open` and so counts as inReview, not decided. + await upsertPullRequestFromGitHub(env, "JSONbored/loopover", { number: 6, title: "recent ok", state: "closed", merged_at: recent, user: { login: "c" }, head: { sha: "s6" }, labels: [] }); + await recordAuditEvent(env, { eventType: "github_app.pr_public_surface_published", targetKey: "JSONbored/loopover#6", outcome: "completed", createdAt: recent }); + + const out = await getPublicStats(env, NOW); + // Lifetime volume still reports every PR ever published -- that part is measured and unaffected. + expect(out.totals.reviewed).toBe(6); + expect(out.totals.merged).toBe(5); + expect(out.totals.reversed).toBe(1); + // Accuracy divides by the WINDOWED denominator (PR#6 merged inside the window), not the lifetime 5. + // 1 - 1/1 = 0%, not the 1 - 1/5 = 80% the old lifetime pairing would have published. + expect(out.byProject[0]?.accuracyPct).toBe(0); + expect(out.totals.accuracyPct).toBe(0); + }); + it("publishes a real accuracy once the deployment records the auto-actions a reversal attaches to (real D1)", async () => { const env = createTestEnv({ LOOPOVER_PUBLIC_STATS_REPOS: "JSONbored/loopover" }); await upsertRepositoryFromGitHub(env, { name: "loopover", full_name: "JSONbored/loopover", private: false, owner: { login: "JSONbored" } }, 1); From 03ac9e184f225436f6205eb974a84e37dd70d313 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Wed, 29 Jul 2026 00:48:40 -0700 Subject: [PATCH 2/2] 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. --- apps/loopover-ui/src/routeTree.gen.ts | 562 +++++++++++++------------- src/review/public-stats.ts | 7 +- 2 files changed, 285 insertions(+), 284 deletions(-) diff --git a/apps/loopover-ui/src/routeTree.gen.ts b/apps/loopover-ui/src/routeTree.gen.ts index 1409d48c93..7ac38456ef 100644 --- a/apps/loopover-ui/src/routeTree.gen.ts +++ b/apps/loopover-ui/src/routeTree.gen.ts @@ -9,63 +9,63 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as RoadmapRouteImport } from './routes/roadmap' -import { Route as MinersRouteImport } from './routes/miners' -import { Route as MaintainersRouteImport } from './routes/maintainers' -import { Route as InstallRouteImport } from './routes/install' -import { Route as FairnessRouteImport } from './routes/fairness' -import { Route as DocsRouteImport } from './routes/docs' -import { Route as ChangelogRouteImport } from './routes/changelog' -import { Route as AppRouteImport } from './routes/app' -import { Route as ApiRouteImport } from './routes/api' -import { Route as AgentsRouteImport } from './routes/agents' import { Route as IndexRouteImport } from './routes/index' -import { Route as InstallIndexRouteImport } from './routes/install.index' -import { Route as DocsIndexRouteImport } from './routes/docs.index' -import { Route as AppIndexRouteImport } from './routes/app.index' +import { Route as AgentsRouteImport } from './routes/agents' +import { Route as ApiRouteImport } from './routes/api' +import { Route as AppRouteImport } from './routes/app' +import { Route as ChangelogRouteImport } from './routes/changelog' +import { Route as DocsRouteImport } from './routes/docs' +import { Route as FairnessRouteImport } from './routes/fairness' +import { Route as InstallRouteImport } from './routes/install' +import { Route as MaintainersRouteImport } from './routes/maintainers' +import { Route as MinersRouteImport } from './routes/miners' +import { Route as RoadmapRouteImport } from './routes/roadmap' import { Route as ApiIndexRouteImport } from './routes/api.index' -import { Route as InstallPermissionsRouteImport } from './routes/install.permissions' -import { Route as DocsFumadocsSpikeApiReferenceRouteImport } from './routes/docs.fumadocs-spike-api-reference' -import { Route as DocsSlugRouteImport } from './routes/docs.$slug' -import { Route as AppWorkbenchRouteImport } from './routes/app.workbench' -import { Route as AppRunsRouteImport } from './routes/app.runs' -import { Route as AppReposRouteImport } from './routes/app.repos' -import { Route as AppPlaygroundRouteImport } from './routes/app.playground' -import { Route as AppOwnerRouteImport } from './routes/app.owner' -import { Route as AppOperatorRouteImport } from './routes/app.operator' -import { Route as AppMinerRouteImport } from './routes/app.miner' -import { Route as AppMaintainerRouteImport } from './routes/app.maintainer' -import { Route as AppDigestRouteImport } from './routes/app.digest' -import { Route as AppConfigGeneratorRouteImport } from './routes/app.config-generator' -import { Route as AppCommandsRouteImport } from './routes/app.commands' -import { Route as AppAuditRouteImport } from './routes/app.audit' -import { Route as AppAnalyticsRouteImport } from './routes/app.analytics' import { Route as ApiOpRouteImport } from './routes/api.$op' +import { Route as AppIndexRouteImport } from './routes/app.index' +import { Route as AppAnalyticsRouteImport } from './routes/app.analytics' +import { Route as AppAuditRouteImport } from './routes/app.audit' +import { Route as AppCommandsRouteImport } from './routes/app.commands' +import { Route as AppConfigGeneratorRouteImport } from './routes/app.config-generator' +import { Route as AppDigestRouteImport } from './routes/app.digest' +import { Route as AppMaintainerRouteImport } from './routes/app.maintainer' +import { Route as AppMinerRouteImport } from './routes/app.miner' +import { Route as AppOperatorRouteImport } from './routes/app.operator' +import { Route as AppOwnerRouteImport } from './routes/app.owner' +import { Route as AppPlaygroundRouteImport } from './routes/app.playground' +import { Route as AppReposRouteImport } from './routes/app.repos' +import { Route as AppRunsRouteImport } from './routes/app.runs' +import { Route as AppWorkbenchRouteImport } from './routes/app.workbench' +import { Route as DocsIndexRouteImport } from './routes/docs.index' +import { Route as DocsSlugRouteImport } from './routes/docs.$slug' +import { Route as DocsFumadocsSpikeApiReferenceRouteImport } from './routes/docs.fumadocs-spike-api-reference' +import { Route as InstallIndexRouteImport } from './routes/install.index' +import { Route as InstallPermissionsRouteImport } from './routes/install.permissions' import { Route as ReposOwnerRepoQualityRouteImport } from './routes/repos.$owner.$repo.quality' -const RoadmapRoute = RoadmapRouteImport.update({ - id: '/roadmap', - path: '/roadmap', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const MinersRoute = MinersRouteImport.update({ - id: '/miners', - path: '/miners', +const AgentsRoute = AgentsRouteImport.update({ + id: '/agents', + path: '/agents', getParentRoute: () => rootRouteImport, } as any) -const MaintainersRoute = MaintainersRouteImport.update({ - id: '/maintainers', - path: '/maintainers', +const ApiRoute = ApiRouteImport.update({ + id: '/api', + path: '/api', getParentRoute: () => rootRouteImport, } as any) -const InstallRoute = InstallRouteImport.update({ - id: '/install', - path: '/install', +const AppRoute = AppRouteImport.update({ + id: '/app', + path: '/app', getParentRoute: () => rootRouteImport, } as any) -const FairnessRoute = FairnessRouteImport.update({ - id: '/fairness', - path: '/fairness', +const ChangelogRoute = ChangelogRouteImport.update({ + id: '/changelog', + path: '/changelog', getParentRoute: () => rootRouteImport, } as any) const DocsRoute = DocsRouteImport.update({ @@ -73,95 +73,74 @@ const DocsRoute = DocsRouteImport.update({ path: '/docs', getParentRoute: () => rootRouteImport, } as any) -const ChangelogRoute = ChangelogRouteImport.update({ - id: '/changelog', - path: '/changelog', +const FairnessRoute = FairnessRouteImport.update({ + id: '/fairness', + path: '/fairness', getParentRoute: () => rootRouteImport, } as any) -const AppRoute = AppRouteImport.update({ - id: '/app', - path: '/app', +const InstallRoute = InstallRouteImport.update({ + id: '/install', + path: '/install', getParentRoute: () => rootRouteImport, } as any) -const ApiRoute = ApiRouteImport.update({ - id: '/api', - path: '/api', +const MaintainersRoute = MaintainersRouteImport.update({ + id: '/maintainers', + path: '/maintainers', getParentRoute: () => rootRouteImport, } as any) -const AgentsRoute = AgentsRouteImport.update({ - id: '/agents', - path: '/agents', +const MinersRoute = MinersRouteImport.update({ + id: '/miners', + path: '/miners', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const RoadmapRoute = RoadmapRouteImport.update({ + id: '/roadmap', + path: '/roadmap', getParentRoute: () => rootRouteImport, } as any) -const InstallIndexRoute = InstallIndexRouteImport.update({ +const ApiIndexRoute = ApiIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => InstallRoute, + getParentRoute: () => ApiRoute, } as any) -const DocsIndexRoute = DocsIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => DocsRoute, +const ApiOpRoute = ApiOpRouteImport.update({ + id: '/$op', + path: '/$op', + getParentRoute: () => ApiRoute, } as any) const AppIndexRoute = AppIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => AppRoute, } as any) -const ApiIndexRoute = ApiIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => ApiRoute, -} as any) -const InstallPermissionsRoute = InstallPermissionsRouteImport.update({ - id: '/permissions', - path: '/permissions', - getParentRoute: () => InstallRoute, -} as any) -const DocsFumadocsSpikeApiReferenceRoute = - DocsFumadocsSpikeApiReferenceRouteImport.update({ - id: '/fumadocs-spike-api-reference', - path: '/fumadocs-spike-api-reference', - getParentRoute: () => DocsRoute, - } as any) -const DocsSlugRoute = DocsSlugRouteImport.update({ - id: '/$slug', - path: '/$slug', - getParentRoute: () => DocsRoute, -} as any) -const AppWorkbenchRoute = AppWorkbenchRouteImport.update({ - id: '/workbench', - path: '/workbench', +const AppAnalyticsRoute = AppAnalyticsRouteImport.update({ + id: '/analytics', + path: '/analytics', getParentRoute: () => AppRoute, } as any) -const AppRunsRoute = AppRunsRouteImport.update({ - id: '/runs', - path: '/runs', +const AppAuditRoute = AppAuditRouteImport.update({ + id: '/audit', + path: '/audit', getParentRoute: () => AppRoute, } as any) -const AppReposRoute = AppReposRouteImport.update({ - id: '/repos', - path: '/repos', +const AppCommandsRoute = AppCommandsRouteImport.update({ + id: '/commands', + path: '/commands', getParentRoute: () => AppRoute, } as any) -const AppPlaygroundRoute = AppPlaygroundRouteImport.update({ - id: '/playground', - path: '/playground', +const AppConfigGeneratorRoute = AppConfigGeneratorRouteImport.update({ + id: '/config-generator', + path: '/config-generator', getParentRoute: () => AppRoute, } as any) -const AppOwnerRoute = AppOwnerRouteImport.update({ - id: '/owner', - path: '/owner', +const AppDigestRoute = AppDigestRouteImport.update({ + id: '/digest', + path: '/digest', getParentRoute: () => AppRoute, } as any) -const AppOperatorRoute = AppOperatorRouteImport.update({ - id: '/operator', - path: '/operator', +const AppMaintainerRoute = AppMaintainerRouteImport.update({ + id: '/maintainer', + path: '/maintainer', getParentRoute: () => AppRoute, } as any) const AppMinerRoute = AppMinerRouteImport.update({ @@ -169,40 +148,61 @@ const AppMinerRoute = AppMinerRouteImport.update({ path: '/miner', getParentRoute: () => AppRoute, } as any) -const AppMaintainerRoute = AppMaintainerRouteImport.update({ - id: '/maintainer', - path: '/maintainer', +const AppOperatorRoute = AppOperatorRouteImport.update({ + id: '/operator', + path: '/operator', getParentRoute: () => AppRoute, } as any) -const AppDigestRoute = AppDigestRouteImport.update({ - id: '/digest', - path: '/digest', +const AppOwnerRoute = AppOwnerRouteImport.update({ + id: '/owner', + path: '/owner', getParentRoute: () => AppRoute, } as any) -const AppConfigGeneratorRoute = AppConfigGeneratorRouteImport.update({ - id: '/config-generator', - path: '/config-generator', +const AppPlaygroundRoute = AppPlaygroundRouteImport.update({ + id: '/playground', + path: '/playground', getParentRoute: () => AppRoute, } as any) -const AppCommandsRoute = AppCommandsRouteImport.update({ - id: '/commands', - path: '/commands', +const AppReposRoute = AppReposRouteImport.update({ + id: '/repos', + path: '/repos', getParentRoute: () => AppRoute, } as any) -const AppAuditRoute = AppAuditRouteImport.update({ - id: '/audit', - path: '/audit', +const AppRunsRoute = AppRunsRouteImport.update({ + id: '/runs', + path: '/runs', getParentRoute: () => AppRoute, } as any) -const AppAnalyticsRoute = AppAnalyticsRouteImport.update({ - id: '/analytics', - path: '/analytics', +const AppWorkbenchRoute = AppWorkbenchRouteImport.update({ + id: '/workbench', + path: '/workbench', getParentRoute: () => AppRoute, } as any) -const ApiOpRoute = ApiOpRouteImport.update({ - id: '/$op', - path: '/$op', - getParentRoute: () => ApiRoute, +const DocsIndexRoute = DocsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => DocsRoute, +} as any) +const DocsSlugRoute = DocsSlugRouteImport.update({ + id: '/$slug', + path: '/$slug', + getParentRoute: () => DocsRoute, +} as any) +const DocsFumadocsSpikeApiReferenceRoute = + DocsFumadocsSpikeApiReferenceRouteImport.update({ + id: '/fumadocs-spike-api-reference', + path: '/fumadocs-spike-api-reference', + getParentRoute: () => DocsRoute, + } as any) +const InstallIndexRoute = InstallIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => InstallRoute, +} as any) +const InstallPermissionsRoute = InstallPermissionsRouteImport.update({ + id: '/permissions', + path: '/permissions', + getParentRoute: () => InstallRoute, } as any) const ReposOwnerRepoQualityRoute = ReposOwnerRepoQualityRouteImport.update({ id: '/repos/$owner/$repo/quality', @@ -433,39 +433,39 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/roadmap': { - id: '/roadmap' - path: '/roadmap' - fullPath: '/roadmap' - preLoaderRoute: typeof RoadmapRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/miners': { - id: '/miners' - path: '/miners' - fullPath: '/miners' - preLoaderRoute: typeof MinersRouteImport + '/agents': { + id: '/agents' + path: '/agents' + fullPath: '/agents' + preLoaderRoute: typeof AgentsRouteImport parentRoute: typeof rootRouteImport } - '/maintainers': { - id: '/maintainers' - path: '/maintainers' - fullPath: '/maintainers' - preLoaderRoute: typeof MaintainersRouteImport + '/api': { + id: '/api' + path: '/api' + fullPath: '/api' + preLoaderRoute: typeof ApiRouteImport parentRoute: typeof rootRouteImport } - '/install': { - id: '/install' - path: '/install' - fullPath: '/install' - preLoaderRoute: typeof InstallRouteImport + '/app': { + id: '/app' + path: '/app' + fullPath: '/app' + preLoaderRoute: typeof AppRouteImport parentRoute: typeof rootRouteImport } - '/fairness': { - id: '/fairness' - path: '/fairness' - fullPath: '/fairness' - preLoaderRoute: typeof FairnessRouteImport + '/changelog': { + id: '/changelog' + path: '/changelog' + fullPath: '/changelog' + preLoaderRoute: typeof ChangelogRouteImport parentRoute: typeof rootRouteImport } '/docs': { @@ -475,54 +475,54 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DocsRouteImport parentRoute: typeof rootRouteImport } - '/changelog': { - id: '/changelog' - path: '/changelog' - fullPath: '/changelog' - preLoaderRoute: typeof ChangelogRouteImport + '/fairness': { + id: '/fairness' + path: '/fairness' + fullPath: '/fairness' + preLoaderRoute: typeof FairnessRouteImport parentRoute: typeof rootRouteImport } - '/app': { - id: '/app' - path: '/app' - fullPath: '/app' - preLoaderRoute: typeof AppRouteImport + '/install': { + id: '/install' + path: '/install' + fullPath: '/install' + preLoaderRoute: typeof InstallRouteImport parentRoute: typeof rootRouteImport } - '/api': { - id: '/api' - path: '/api' - fullPath: '/api' - preLoaderRoute: typeof ApiRouteImport + '/maintainers': { + id: '/maintainers' + path: '/maintainers' + fullPath: '/maintainers' + preLoaderRoute: typeof MaintainersRouteImport parentRoute: typeof rootRouteImport } - '/agents': { - id: '/agents' - path: '/agents' - fullPath: '/agents' - preLoaderRoute: typeof AgentsRouteImport + '/miners': { + id: '/miners' + path: '/miners' + fullPath: '/miners' + preLoaderRoute: typeof MinersRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/roadmap': { + id: '/roadmap' + path: '/roadmap' + fullPath: '/roadmap' + preLoaderRoute: typeof RoadmapRouteImport parentRoute: typeof rootRouteImport } - '/install/': { - id: '/install/' + '/api/': { + id: '/api/' path: '/' - fullPath: '/install/' - preLoaderRoute: typeof InstallIndexRouteImport - parentRoute: typeof InstallRoute + fullPath: '/api/' + preLoaderRoute: typeof ApiIndexRouteImport + parentRoute: typeof ApiRoute } - '/docs/': { - id: '/docs/' - path: '/' - fullPath: '/docs/' - preLoaderRoute: typeof DocsIndexRouteImport - parentRoute: typeof DocsRoute + '/api/$op': { + id: '/api/$op' + path: '/$op' + fullPath: '/api/$op' + preLoaderRoute: typeof ApiOpRouteImport + parentRoute: typeof ApiRoute } '/app/': { id: '/app/' @@ -531,74 +531,46 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppIndexRouteImport parentRoute: typeof AppRoute } - '/api/': { - id: '/api/' - path: '/' - fullPath: '/api/' - preLoaderRoute: typeof ApiIndexRouteImport - parentRoute: typeof ApiRoute - } - '/install/permissions': { - id: '/install/permissions' - path: '/permissions' - fullPath: '/install/permissions' - preLoaderRoute: typeof InstallPermissionsRouteImport - parentRoute: typeof InstallRoute - } - '/docs/fumadocs-spike-api-reference': { - id: '/docs/fumadocs-spike-api-reference' - path: '/fumadocs-spike-api-reference' - fullPath: '/docs/fumadocs-spike-api-reference' - preLoaderRoute: typeof DocsFumadocsSpikeApiReferenceRouteImport - parentRoute: typeof DocsRoute - } - '/docs/$slug': { - id: '/docs/$slug' - path: '/$slug' - fullPath: '/docs/$slug' - preLoaderRoute: typeof DocsSlugRouteImport - parentRoute: typeof DocsRoute - } - '/app/workbench': { - id: '/app/workbench' - path: '/workbench' - fullPath: '/app/workbench' - preLoaderRoute: typeof AppWorkbenchRouteImport + '/app/analytics': { + id: '/app/analytics' + path: '/analytics' + fullPath: '/app/analytics' + preLoaderRoute: typeof AppAnalyticsRouteImport parentRoute: typeof AppRoute } - '/app/runs': { - id: '/app/runs' - path: '/runs' - fullPath: '/app/runs' - preLoaderRoute: typeof AppRunsRouteImport + '/app/audit': { + id: '/app/audit' + path: '/audit' + fullPath: '/app/audit' + preLoaderRoute: typeof AppAuditRouteImport parentRoute: typeof AppRoute } - '/app/repos': { - id: '/app/repos' - path: '/repos' - fullPath: '/app/repos' - preLoaderRoute: typeof AppReposRouteImport + '/app/commands': { + id: '/app/commands' + path: '/commands' + fullPath: '/app/commands' + preLoaderRoute: typeof AppCommandsRouteImport parentRoute: typeof AppRoute } - '/app/playground': { - id: '/app/playground' - path: '/playground' - fullPath: '/app/playground' - preLoaderRoute: typeof AppPlaygroundRouteImport + '/app/config-generator': { + id: '/app/config-generator' + path: '/config-generator' + fullPath: '/app/config-generator' + preLoaderRoute: typeof AppConfigGeneratorRouteImport parentRoute: typeof AppRoute } - '/app/owner': { - id: '/app/owner' - path: '/owner' - fullPath: '/app/owner' - preLoaderRoute: typeof AppOwnerRouteImport + '/app/digest': { + id: '/app/digest' + path: '/digest' + fullPath: '/app/digest' + preLoaderRoute: typeof AppDigestRouteImport parentRoute: typeof AppRoute } - '/app/operator': { - id: '/app/operator' - path: '/operator' - fullPath: '/app/operator' - preLoaderRoute: typeof AppOperatorRouteImport + '/app/maintainer': { + id: '/app/maintainer' + path: '/maintainer' + fullPath: '/app/maintainer' + preLoaderRoute: typeof AppMaintainerRouteImport parentRoute: typeof AppRoute } '/app/miner': { @@ -608,54 +580,82 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppMinerRouteImport parentRoute: typeof AppRoute } - '/app/maintainer': { - id: '/app/maintainer' - path: '/maintainer' - fullPath: '/app/maintainer' - preLoaderRoute: typeof AppMaintainerRouteImport + '/app/operator': { + id: '/app/operator' + path: '/operator' + fullPath: '/app/operator' + preLoaderRoute: typeof AppOperatorRouteImport parentRoute: typeof AppRoute } - '/app/digest': { - id: '/app/digest' - path: '/digest' - fullPath: '/app/digest' - preLoaderRoute: typeof AppDigestRouteImport + '/app/owner': { + id: '/app/owner' + path: '/owner' + fullPath: '/app/owner' + preLoaderRoute: typeof AppOwnerRouteImport parentRoute: typeof AppRoute } - '/app/config-generator': { - id: '/app/config-generator' - path: '/config-generator' - fullPath: '/app/config-generator' - preLoaderRoute: typeof AppConfigGeneratorRouteImport + '/app/playground': { + id: '/app/playground' + path: '/playground' + fullPath: '/app/playground' + preLoaderRoute: typeof AppPlaygroundRouteImport parentRoute: typeof AppRoute } - '/app/commands': { - id: '/app/commands' - path: '/commands' - fullPath: '/app/commands' - preLoaderRoute: typeof AppCommandsRouteImport + '/app/repos': { + id: '/app/repos' + path: '/repos' + fullPath: '/app/repos' + preLoaderRoute: typeof AppReposRouteImport parentRoute: typeof AppRoute } - '/app/audit': { - id: '/app/audit' - path: '/audit' - fullPath: '/app/audit' - preLoaderRoute: typeof AppAuditRouteImport + '/app/runs': { + id: '/app/runs' + path: '/runs' + fullPath: '/app/runs' + preLoaderRoute: typeof AppRunsRouteImport parentRoute: typeof AppRoute } - '/app/analytics': { - id: '/app/analytics' - path: '/analytics' - fullPath: '/app/analytics' - preLoaderRoute: typeof AppAnalyticsRouteImport + '/app/workbench': { + id: '/app/workbench' + path: '/workbench' + fullPath: '/app/workbench' + preLoaderRoute: typeof AppWorkbenchRouteImport parentRoute: typeof AppRoute } - '/api/$op': { - id: '/api/$op' - path: '/$op' - fullPath: '/api/$op' - preLoaderRoute: typeof ApiOpRouteImport - parentRoute: typeof ApiRoute + '/docs/': { + id: '/docs/' + path: '/' + fullPath: '/docs/' + preLoaderRoute: typeof DocsIndexRouteImport + parentRoute: typeof DocsRoute + } + '/docs/$slug': { + id: '/docs/$slug' + path: '/$slug' + fullPath: '/docs/$slug' + preLoaderRoute: typeof DocsSlugRouteImport + parentRoute: typeof DocsRoute + } + '/docs/fumadocs-spike-api-reference': { + id: '/docs/fumadocs-spike-api-reference' + path: '/fumadocs-spike-api-reference' + fullPath: '/docs/fumadocs-spike-api-reference' + preLoaderRoute: typeof DocsFumadocsSpikeApiReferenceRouteImport + parentRoute: typeof DocsRoute + } + '/install/': { + id: '/install/' + path: '/' + fullPath: '/install/' + preLoaderRoute: typeof InstallIndexRouteImport + parentRoute: typeof InstallRoute + } + '/install/permissions': { + id: '/install/permissions' + path: '/permissions' + fullPath: '/install/permissions' + preLoaderRoute: typeof InstallPermissionsRouteImport + parentRoute: typeof InstallRoute } '/repos/$owner/$repo/quality': { id: '/repos/$owner/$repo/quality' diff --git a/src/review/public-stats.ts b/src/review/public-stats.ts index 3c20055f47..74a5724c88 100644 --- a/src/review/public-stats.ts +++ b/src/review/public-stats.ts @@ -639,9 +639,10 @@ export async function getPublicStats( ...totals, reviewed, filteredPct: filteredPct(reviewed, totals.merged), - // Option 1 of #7449: compute the global accuracy from the OWN-LEDGER merged/closed snapshot (not the - // fleet-folded totals.merged/closed), so its numerator (own-ledger reversed) and denominator are drawn - // from the same population. See the ownLedgerMerged/ownLedgerClosed snapshot above the Orb fold for why. + // #7449 extended: compute the global accuracy from windowedMerged/windowedClosed (accumulated in the + // byProject fold above), never the fleet-folded lifetime totals.merged/closed, so the numerator + // (own-ledger `reversed`) and the denominator are drawn from the same population AND the same + // retention window. See the windowed disposition query's own comment for both halves of that pairing. accuracyPct: accuracyPct(windowedMerged, windowedClosed, totals.reversed, reversalObservable), minutesSaved, },