diff --git a/README.md b/README.md
index 6af6ee4..5fb6c60 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,8 @@ RF4 Spotter — неофициальный сервис свежих точек
Подробный план и актуальные чекбоксы находятся в [`docs/ROADMAP.md`](docs/ROADMAP.md).
+Актуальная инвентаризация источников, проверка парсеров и правила подключения новых адаптеров находятся в [`docs/data-source-audit.md`](docs/data-source-audit.md). Сейчас проект использует официальный HTML рекордов и собственную модерируемую форму; сторонние базы не импортируются без согласования.
+
## Запуск через Docker
Требуются Docker Engine и Docker Compose. Это основной и рекомендуемый сценарий:
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index e14eb16..0773dbc 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -64,6 +64,16 @@
- [ ] Выбрать лицензию кода и политику использования данных.
- [ ] Заменить демонстрационные секреты и определить целевое размещение перед внешней публикацией.
+## Источники данных и согласование
+
+- [x] Провести аудит подключённых источников, потенциальных поставщиков и всех существующих парсеров; результат записан в `docs/data-source-audit.md`.
+- [x] Проверить оба официальных HTML-парсера на актуальной странице и добавить общий контрактный тест эквивалентности.
+- [ ] Добавить `data_source` и языковые/версионные алиасы рыб, водоёмов и приманок до подключения второго автоматического источника.
+- [ ] Вынести общий официальный DOM-парсер, устранив дублирование исследовательской и продуктивной реализации.
+- [ ] Добавить отдельную фикстуру и безопасный ручной импорт недельных официальных рекордов одной категории.
+- [ ] Получить явное разрешение или документированный API/экспорт RF4DB и RF4-STAT до реализации адаптеров.
+- [ ] Согласовать один добровольный канал сообщества и правила происхождения, модерации и удаления сообщений.
+
## Этап 5 — пилот
- [ ] Согласовать первые категории рекордов, водоёмы и виды рыб.
diff --git a/docs/data-source-audit.md b/docs/data-source-audit.md
new file mode 100644
index 0000000..6b8e3e2
--- /dev/null
+++ b/docs/data-source-audit.md
@@ -0,0 +1,113 @@
+# Аудит источников и парсеров
+
+Дата проверки: **3 сентября 2026 года**. Аудит охватывает код репозитория, один контрольный запрос к официальной таблице и публично описанные возможности потенциальных источников. Он не является разрешением на сбор чужих данных.
+
+## Итог
+
+Сейчас у проекта **два реально работающих канала данных**, но только **один автоматический внешний источник**:
+
+| Канал | Статус | Что даёт | Координаты | Доверие |
+|---|---|---|---|---|
+| Официальная таблица `rf4game.de` | подключена, HTML-импорт | рекорд, рыба, вес, водоём, приманка, игрок, дата | нет | 100 |
+| Собственная форма RF4 Spotter | подключена, после модерации | обычный улов, точка, снасть, игрок, скриншот | да | назначается приложением |
+
+`manual_import` существует в модели и seed, но отдельного пользовательского CSV/JSON-импортера нет. Seed — демонстрационные данные, а не источник. MinIO хранит доказательство пользовательской записи и также не является самостоятельным источником.
+
+## Текущие парсеры
+
+В репозитории два HTML-парсера одной официальной таблицы:
+
+1. `rf4_research.records.parse_records_html` — исследовательский адаптер и CLI;
+2. `app.importer.parse_html` — продуктивный адаптер с HTTP-кэшем, повторами, журналом и сохранением.
+
+Оба ожидают один DOM-контракт `div.records.flex_table`, одинаковый порядок шести колонок и одинаково нормализуют вес, дату и пустые поля. Добавлен общий контрактный тест: на одной фикстуре парсеры обязаны вернуть одинаковые кортежи полей и оба обязаны отклонить изменённую структуру колонок.
+
+Контрольный HTML `https://rf4game.de/records/region/RU/` имел размер 1 147 837 байт. Оба парсера успешно прочитали **1260 записей**, результаты по восьми общим полям полностью совпали. Временный HTML в репозиторий не добавлен.
+
+### Обнаруженные ограничения согласования
+
+- Дублирование кода двух парсеров остаётся риском. Контрактный тест защищает результат, но при следующем рефакторинге общую чистую функцию разбора лучше вынести в пакет, доступный и исследовательскому CLI, и API-контейнеру.
+- Источник настроен на немецкий домен. Регион `RU` фильтрует игроков, но названия рыб, водоёмов и приманок остаются немецкими. Они не совпадают с русскими названиями пользовательской формы и могут создать параллельные сущности.
+- Русский URL `https://rf4game.ru/records/region/RU/` при контрольном запросе вернул небольшую JavaScript-защитную страницу без таблицы; текущий парсер корректно завершился ошибкой `records table not found`. Обход защиты не рассматривается.
+- `source_external_id` надёжно убирает повтор одной и той же локализованной строки, но локализованные копии одной записи получат разные хеши. Нельзя считать разные языковые домены независимыми подтверждениями.
+- Официальная запись не содержит координат и поэтому не участвует в индексе конкретной точки. Автоматически связывать её с точкой нельзя.
+- Для новых агрегаторов одного enum `source_type` недостаточно: нужен стабильный `source_system`, внешний ID внутри этого источника, исходная ссылка и отдельное правило доверия.
+
+## Где можно получить новые данные
+
+### Приоритет A — тот же официальный источник
+
+Официальная навигация публикует абсолютные и недельные таблицы, а также категории `records`, `ultralight`, `recordslight`, `bottomlight`, `sea` и `telestick`. Это расширение охвата существующего источника, а не независимые подтверждения. Текущий DOM-парсер, вероятно, можно переиспользовать, но каждую комбинацию нужно сначала проверить отдельной фикстурой. Регулярный обход нельзя включать без согласования допустимой частоты с владельцем сайта.
+
+Рекомендуемый первый эксперимент — **недельные RU-рекорды одной категории**. Они полезнее абсолютных для свежести, не требуют новой схемы, но всё ещё не дают координат.
+
+### Приоритет A — согласованные каналы сообщества
+
+Telegram, Discord и VK могут давать свежие координаты, оснастку, игровое время и скриншоты. Подключать следует только конкретные каналы, администраторы которых письменно разрешили импорт. Для каждого сообщения нужны permalink/message ID, время публикации, имя канала, версия правил и возможность удалить запись по запросу.
+
+Наиболее безопасная реализация — бот или форма, куда автор сам пересылает сообщение и подтверждает распознанные поля. Это лучше скрытого чтения групп и позволяет использовать существующую очередь модерации.
+
+### Приоритет B — RF4DB по договорённости
+
+`https://rf4db.com/ru` показывает актуальные пользовательские уловы, координаты, игровое время, погоду, снасть и изображения; публичная страница заявляет 19 водоёмов, 252 вида рыб и тысячи точек. Страница об источниках поясняет, что игровые справочники взяты из игры, а точки и комментарии собраны игроками в открытом доступе.
+
+Технически HTML пригоден для адаптера, но это уже собранная база другого проекта. До письменного разрешения владельца нельзя делать парсер или копировать изображения. Лучший вариант — запросить документированный экспорт/API и условия атрибуции, удаления и частоты обновления.
+
+### Приоритет B — RF4-STAT по договорённости
+
+`https://rf4-stat.ru/help/` сообщает, что рекорды берутся с официального сайта, а точки и посты — из VK, Discord, Telegram и собственной формы. Сервис обновляет записи регулярно, часть доступа является Premium.
+
+Парсинг страниц означал бы повторный сбор уже агрегированных материалов и мог бы обходить продуктовые ограничения. Использовать источник можно только через согласованный API/выгрузку; данные, пришедшие туда с официального сайта, нельзя считать вторым независимым подтверждением.
+
+### Приоритет C — справочники
+
+Списки рыб, водоёмов, снастей, трофейных весов и переводов полезны для канонизации, но не для оценки текущего клёва. Источниками-кандидатами являются официальные страницы/патчноуты и разрешённый справочный экспорт партнёра. Нужны версия игры, язык и устойчивый внутренний ключ; сопоставление только по отображаемому имени недостаточно.
+
+## Что не использовать
+
+- перехват трафика клиента, внедрение в игру и закрытые протоколы;
+- боты автоматической рыбалки и данные, полученные с нарушением правил игры;
+- обход JavaScript-защиты, CAPTCHA, авторизации или Premium-доступа;
+- массовое копирование чужих изображений и пользовательских постов без разрешения;
+- разные локализации официальной записи как разные подтверждения.
+
+## Схема согласования новых адаптеров
+
+Каждый адаптер должен выдавать промежуточную запись со следующими группами полей:
+
+```text
+provenance: source_system, source_external_id, source_url, observed_at, fetched_at
+identity: fish_external_id/name/language, waterbody_external_id/name/language
+catch: weight_g, caught_at, game_time, bait, method, rig, retrieve
+spot: x, y или null
+actor: player_name или null
+evidence: screenshot_url/key, raw_payload
+quality: moderation_status, source_confidence
+```
+
+До записи в `catch_report` должны последовательно выполняться:
+
+1. валидация контракта источника;
+2. нормализация единиц, времени и пустых значений;
+3. сопоставление рыб/водоёмов по таблице алиасов с языком и версией игры;
+4. дедупликация внутри `source_system`;
+5. выявление кросс-источниковых копий без автоматического объединения сомнительных записей;
+6. назначение доверия и модерации по политике источника;
+7. сохранение происхождения и минимально необходимого сырого фрагмента.
+
+## Рекомендуемый порядок работ
+
+1. Добавить сущности `data_source` и `entity_alias`; включить источник в уникальный ключ внешней записи.
+2. Вынести общий официальный DOM-парсер и оставить два тонких клиента вокруг него.
+3. Добавить фикстуру недельной таблицы и проверить одну официальную категорию без расписания.
+4. Согласовать экспорт/API с RF4DB и RF4-STAT; до ответа не писать их парсеры.
+5. Выбрать один добровольный Telegram/Discord/VK-канал для пилота и зафиксировать согласие/правила удаления.
+6. Только затем добавлять адаптер, контрактные тесты и калибровку `source_confidence`.
+
+## Проверенные ссылки
+
+- официальный абсолютный рейтинг:
+- официальный недельный рейтинг:
+- RF4DB: и
+- RF4-STAT:
+- исследовательский проект:
diff --git a/tests/test_parser_contract.py b/tests/test_parser_contract.py
new file mode 100644
index 0000000..3f05b85
--- /dev/null
+++ b/tests/test_parser_contract.py
@@ -0,0 +1,45 @@
+from pathlib import Path
+
+import pytest
+
+from app.importer import ImportSourceError, parse_html
+from rf4_research.records import RecordsParseError, parse_records_html
+
+
+FIXTURE = Path(__file__).parent / "fixtures" / "records_ru_sample.html"
+
+
+def _research_contract(html: str) -> list[tuple[object, ...]]:
+ records = parse_records_html(
+ html,
+ region="RU",
+ category="records",
+ source_url="fixture://records",
+ )
+ return [
+ (row.region, row.category, row.player, row.fish, row.weight_g, row.waterbody, row.bait, row.record_date)
+ for row in records
+ ]
+
+
+def _production_contract(html: str) -> list[tuple[object, ...]]:
+ records = parse_html(html, region="RU", category="records")
+ return [
+ (row.region, row.category, row.player, row.fish, row.weight_g, row.waterbody, row.bait, row.record_date)
+ for row in records
+ ]
+
+
+def test_research_and_production_parsers_emit_the_same_contract() -> None:
+ html = FIXTURE.read_text(encoding="utf-8")
+
+ assert _research_contract(html) == _production_contract(html)
+
+
+def test_both_parsers_reject_a_changed_column_contract() -> None:
+ html = FIXTURE.read_text(encoding="utf-8").replace('class="col data"', 'class="col changed"', 1)
+
+ with pytest.raises(RecordsParseError, match="records columns changed"):
+ _research_contract(html)
+ with pytest.raises(ImportSourceError, match="record columns changed"):
+ _production_contract(html)