Summary
The "Gittensory — Reviews & PRs (maintainer)" Grafana dashboard's "Manual review" stat panel matches "PRs tracked" exactly (472/472), and every row in the "Pull requests (latest 1000)" table shows verdict=manual regardless of its actual status (many of those same rows correctly show status=merged/status=closed).
Root cause
scripts/export-grafana-reporting-db.sh builds the reporting-database review_targets.verdict column purely from advisories.conclusion:
CASE a.conclusion
WHEN 'success' THEN 'merge'
WHEN 'failure' THEN 'close'
WHEN 'action_required' THEN 'manual'
WHEN 'neutral' THEN 'manual'
WHEN 'skipped' THEN 'ignore'
ELSE NULL
END AS verdict,
Unlike the status column immediately above it (which checks p.state/p.merged_at FIRST and only falls back to a.conclusion for still-open PRs), verdict never consults the PR's own terminal state at all.
Live production data confirms advisories.conclusion is stuck at only neutral (53,841 rows) and action_required (624 rows) — success/failure/skipped never occur in the current dataset (a separate, pre-existing gap: persistAdvisory in src/db/repositories.ts has zero callers anywhere in src/, so nothing ever writes a success/failure/skipped row to advisories for a live PR). Since both surviving values map to 'manual', verdict is mathematically forced to 'manual' for every PR that passes through this join, independent of whether the PR later merged or closed cleanly.
Fix
Mirror status's own precedence into verdict's CASE (in both the Postgres-source and SQLite-source blocks of the exporter script): check p.state/p.merged_at first, and only fall back to a.conclusion for a PR that's still open. This fixes every merged/closed row (the overwhelming majority of the dashboard) immediately, using the exact same terminal-state precedence status already relies on.
Still-open PRs are unaffected — they still resolve verdict from the same (separately broken) advisories.conclusion column as before. Wiring up the deeper gap (nothing ever calls persistAdvisory for a live gate evaluation) is a bigger, separate application-code change tracked as a follow-up, not bundled into this reporting-script fix.
Summary
The "Gittensory — Reviews & PRs (maintainer)" Grafana dashboard's "Manual review" stat panel matches "PRs tracked" exactly (472/472), and every row in the "Pull requests (latest 1000)" table shows
verdict=manualregardless of its actualstatus(many of those same rows correctly showstatus=merged/status=closed).Root cause
scripts/export-grafana-reporting-db.shbuilds the reporting-databasereview_targets.verdictcolumn purely fromadvisories.conclusion:Unlike the
statuscolumn immediately above it (which checksp.state/p.merged_atFIRST and only falls back toa.conclusionfor still-open PRs),verdictnever consults the PR's own terminal state at all.Live production data confirms
advisories.conclusionis stuck at onlyneutral(53,841 rows) andaction_required(624 rows) —success/failure/skippednever occur in the current dataset (a separate, pre-existing gap:persistAdvisoryinsrc/db/repositories.tshas zero callers anywhere insrc/, so nothing ever writes asuccess/failure/skippedrow toadvisoriesfor a live PR). Since both surviving values map to'manual',verdictis mathematically forced to'manual'for every PR that passes through this join, independent of whether the PR later merged or closed cleanly.Fix
Mirror
status's own precedence intoverdict's CASE (in both the Postgres-source and SQLite-source blocks of the exporter script): checkp.state/p.merged_atfirst, and only fall back toa.conclusionfor a PR that's still open. This fixes every merged/closed row (the overwhelming majority of the dashboard) immediately, using the exact same terminal-state precedencestatusalready relies on.Still-open PRs are unaffected — they still resolve
verdictfrom the same (separately broken)advisories.conclusioncolumn as before. Wiring up the deeper gap (nothing ever callspersistAdvisoryfor a live gate evaluation) is a bigger, separate application-code change tracked as a follow-up, not bundled into this reporting-script fix.