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 f5866ba45b..74a5724c88 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). @@ -610,10 +639,11 @@ 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. - accuracyPct: accuracyPct(ownLedgerMerged, ownLedgerClosed, totals.reversed, reversalObservable), + // #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, }, 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);