D243 — Structural lint and supersede relations¶
Decision. Lint gains five checks on the shape of the link graph and on the
superseded_by relation: island, cut_concept, link_to_retired and
map_misfit (info), and broken_relation (warning). They are computed once
per run on the whole KB and reported only for concepts in scope. superseded_by
stays a frontmatter relation that lint consults, not a graph edge. supersede
now refuses a missing successor and a concept superseding itself, and updates
both search indexes. concept_move rewrites a superseded_by that names a
moved concept, as it rewrites body links.
Why. Lint's only graph checks were broken_link and orphan, and the
commonest rot in an operational KB was invisible: live pages still citing
retired ones, groups of pages cut off from the rest, and successors that moved
while their predecessors kept pointing at the old id. supersede wrote without
touching the search indexes, so search showed the old status until a
reconcile.
The thresholds, each a named constant in internal/lint:
- island from 2 concepts: a lone one is already an orphan.
- cut_concept from 3 separated concepts: a page with a child or two is the
normal shape of a wiki.
- map_misfit from 4 neighbours in maps with a two-thirds majority in one
other map: below that, the right map is a matter of taste.
"Retired" is deprecated or superseded; disputed is not. link_to_retired
fires only from maps: an incident in a journal legitimately cites a component
that is gone today.
Alternatives rejected.
- superseded_by as a graph edge: it would change orphan and every degree
for every existing KB.
- Community detection for map_misfit: assignments shift with unrelated edits,
and the finding would flicker; a neighbour majority is direct and explainable.
- Computing on the scoped subgraph: a concept would get a different verdict
depending on how lint was called.
- island as suppressible: it belongs to a component, not to any one
concept's frontmatter.
Consequences. Existing KBs will show new info findings and possibly
broken_relation warnings; none is an error, so no gate_check that passes
today starts failing. The graph algorithms (WeakComponents,
ArticulationPoints) are in internal/graphalgo, checked against a
brute-force reference. A missing successor outside what the caller may write is
refused by the policy before the handler, with the same text as a hidden one.
Because the structural checks read the whole graph, island, cut_concept,
link_to_retired and map_misfit (lint.WholeGraphChecks) are withheld from
a caller that cannot see the whole KB, as the Atlas UI's lint route serves
narrowed tokens: a cut_concept on a visible page counts and names the hidden
concepts it cuts off. The browser suite's non-disclosure test found this.