**Class:** unwanted · **Severity:** material
**Flow:** arch-0a3c2d6d54a6 (maintenance) · **Step:** `docir lint --deep`
**Frequency:** every run against a corpus whose related documents are actually linked

## Finding

The `duplicate` heuristic compares every pair of vectors and reports any pair above the
similarity threshold. It does not look at the relation graph, so a pair the author has
explicitly linked — modelled as related, which is the whole point of typed edges — is
reported as a possible DRY violation.

## What happens today

OBSERVED against docir's own 99-document store. `docir lint --deep` returns 21 findings:
14 `duplicate` and 7 `scope-creep`. **All 14 duplicate pairs are already joined by a
`relates_to` edge** — each is a `Q-0NN` question and the `GAP-0NN` it came from, a pair
that by construction restates the same problem and is linked precisely to say so.

## Impact

The finding has no action behind it. "These two documents are similar" is answered by the
edge already in the file: yes, and here is how they relate. Nothing the user can do clears
it except deleting a document or unlinking a correct relation.

This is the failure mode the layering check already had and had to be fixed for
(BR-045/issue-40d1792bc9f9): a heuristic firing on correct, modelled usage teaches people to ignore
the whole command. `lint --deep` is Tier 2 and never blocks, so it is cheaper than the
layering case was — but a check whose every finding on the product's own corpus is
unactionable is a check nobody runs twice, and the `duplicate` heuristic is the one that
would catch a genuine copy-paste.

## Proposed default

Suppress a `duplicate` finding when the two documents are joined by an edge in either
direction: the graph has already answered it. Keep reporting unlinked similar pairs, which
is the case worth surfacing — two documents nobody has noticed are about the same thing.
Verify the way the layering fix was verified: run it against this corpus and confirm the
count drops to the unlinked pairs only, and that a deliberately unlinked duplicate is still
reported.

## Actors affected

- repository maintainer
- AI coding agent

## Evidence

- `src/docir/modules/documents/application/services/maintenance_service.py` (`lint_deep`)
- PROBE-L1 in the 2026-07-30 probe log

## Resolution

FIXED 2026-07-30. `SimilarityLinter.find_duplicates` takes the set of linked pairs and skips any pair joined by a relation in either direction; `MaintenanceService.lint_deep` supplies it from `uow.documents.relations()`. Against docir's own corpus `lint --deep` goes from 21 findings to 8 — all 14 duplicates were linked pairs, which is the measurement that motivated the change. The unlinked case, which is the copy-paste this check exists to catch, is still reported and has its own test: without it, "no duplicates" and "duplicates are never reported" look identical. `linked_pairs` defaults to empty rather than being required, because this is a pure domain service and a caller with no relation graph should get the unfiltered answer rather than a signature it cannot satisfy. Verified by injecting the bug: with the argument dropped at the call site, the integration guard fails.