docir — design documents Graph

Documents / Issues / issue-44875a5a6ca6

The cycle check counts symmetric `relates_to` edges, so a mutual reference is a permanent warning

A cycle is only meaningful for relation kinds that assert direction; counting the default symmetric kind made 120 correct edges unrecordable in this store.

issue-44875a5a6ca6issueresolved#integrity#material
View as Markdown◉ View in graph

Class: incorrect · Severity: material Flow: arch-0a3c2d6d54a6 · Step: docir check

Finding

_find_cycles (graph_checks.py:285-288) builds its adjacency from every relation, regardless of kind. relates_to — the default kind, and the one a bare id in related: means — asserts no direction: "A relates to B" and "B relates to A" are the same claim. Two documents that each name the other therefore form a two-node cycle that check reports, permanently, on a corpus that is modelled correctly.

This is the same defect class the layering check already fixed. _DEPENDENCY_KINDS exists precisely because reading every kind as a dependency made the most natural thing a user can model into a warning no edit could silence. The cycle check never got the same treatment.

What happens today

OBSERVED, at scale, in this store. Converting the corpus's prose cross-references into typed edges proposed 260 new relates_to edges. Adding all of them takes docir check from 0 findings to 127 cycles — every one of them a mutually-referencing pair that is correctly modelled. Restricting the pass to a cycle-free subset was the only way to keep check usable, and it dropped 120 of the 260 edges: a flow document may no longer link the gaps found in it, because each of those gaps already links back to the flow.

Impact

The graph is the feature that distinguishes docir from a folder of files, and this makes half of it unrecordable. The alternative — record the edges and accept 127 warnings — teaches people to ignore check output, which is where the duplicate-id detection lives. That is the failure mode check --strict's severity split was introduced to end.

Proposed direction

Give _find_cycles its own kind allowlist, as the layering check has. A cycle is only meaningful for kinds that assert directionsupersedes, depends_on, refines, implements. relates_to and contradicts are symmetric and should not contribute edges to the cycle graph at all. Keep the constant separate from _DEPENDENCY_KINDS: they answer different questions and have already diverged once.

Resolution

FIXED. _find_cycles now builds its adjacency from _DIRECTED_KINDS (supersedes, depends_on, refines, implements) only. relates_to and contradicts are symmetric and contribute no edge, so a mutually-referencing pair is no longer a finding.

One exception, found by a test rather than by reasoning: a self-edge is reported whatever its kind. Symmetry is what makes a mutual pair legitimate and it is exactly what makes "A relates to A" empty, so the narrowing had to stop short of it — check is the only thing that sees a self-edge a merge or a hand-edit put on disk (issue-2ebfc018f29a). The first version of this fix silently dropped that detection and test_a_self_edge_already_on_disk_is_still_reported caught it.

The 101 edges the old rule had blocked were added in the same pass: every document that cites another by id in its prose now carries the edge. docir check reports nothing, and no cycle finding was traded for them.

To amend: Re-verify: