Summary
The Dashboard Overview "Orphan pages" KPI and the Wiki Linter use two different definitions of "orphan," so they report different numbers for the same wiki. On the demo dataset the Overview showed 8 orphans while the Linter showed 1.
Root cause
- Overview KPI —
app/services/stats_aggregator.py (wiki.pages.orphan metric): counted every page across all scopes whose slug has no inbound [[wikilink]], including reserved system pages (_index, _log, _hot). Per-scope copies of _index/_log inflated the count.
- Wiki Linter —
app/services/wiki_service.py (diagnose_*, ~line 1297): correctly excludes reserved slugs (INDEX_SLUG/LOG_SLUG/HOT_SLUG) and defaults to scope=global.
Result: _index×3 + _log×4 + a stray seed page = 8 in the Overview; the Linter's exclusion gave 1.
Secondary issue
The Overview KPI is a pre-aggregated single number (stored in stats_daily_rollup) with no drill-down — clicking it doesn't show which pages are orphaned. The Linter is the only place that lists them.
Fix applied (local)
stats_aggregator.py orphan query now excludes reserved slugs, matching the linter:
select(func.count(WikiPage.id)).where(
WikiPage.slug.notin_(select(inbound_subq.c.to_slug)),
WikiPage.slug.notin_(["_index", "_log", "_hot"]),
)
After a backend rebuild + rollup re-run, the stored wiki.pages.orphan metric is now 0 and agrees with the linter.
Follow-up (not done)
Consider wiring the Overview KPI to a drill-down (reuse the linter's orphan list) so the number is actionable.
Summary
The Dashboard Overview "Orphan pages" KPI and the Wiki Linter use two different definitions of "orphan," so they report different numbers for the same wiki. On the demo dataset the Overview showed 8 orphans while the Linter showed 1.
Root cause
app/services/stats_aggregator.py(wiki.pages.orphanmetric): counted every page across all scopes whose slug has no inbound[[wikilink]], including reserved system pages (_index,_log,_hot). Per-scope copies of_index/_loginflated the count.app/services/wiki_service.py(diagnose_*, ~line 1297): correctly excludes reserved slugs (INDEX_SLUG/LOG_SLUG/HOT_SLUG) and defaults toscope=global.Result:
_index×3 +_log×4 + a stray seed page = 8 in the Overview; the Linter's exclusion gave 1.Secondary issue
The Overview KPI is a pre-aggregated single number (stored in
stats_daily_rollup) with no drill-down — clicking it doesn't show which pages are orphaned. The Linter is the only place that lists them.Fix applied (local)
stats_aggregator.pyorphan query now excludes reserved slugs, matching the linter:After a backend rebuild + rollup re-run, the stored
wiki.pages.orphanmetric is now0and agrees with the linter.Follow-up (not done)
Consider wiring the Overview KPI to a drill-down (reuse the linter's orphan list) so the number is actionable.