Вики с проверкой фактов: спецификация
Версия от 2026-10-22.
1 Назначение и границы
Вики публикует знание, которое прошло проверку, и честно показывает читателю, что проверку ещё не прошло. Каждая страница несёт видимый статус достоверности, каждое утверждение опирается на источник или помечено как неподтверждённое, а мнение отделено от факта разметкой, которую нельзя пропустить при чтении. Читатель по умолчанию видит последнюю проверенную версию страницы. Более свежая непроверенная правка доступна по ссылке с предупреждением.
Движок живёт в отдельном репозитории и ничего не знает о DV Hub. Для DV Hub вики служит вершиной воронки «материал → тема → дискуссия → синтез → публикация»: синтез, готовый к выходу наружу, превращается в страницу вики и проходит ту же проверку, что и любая другая статья (раздел 2). Другое сообщество может поднять тот же движок без DV Hub.
Сервер выполняет только дешёвые операции: разбор Markdown при сохранении, обновление таблиц ссылок и категорий, полнотекстовый поиск SQLite, сравнение ревизий, подбор места в дереве по словам, ночные подсчёты и проверку внешних ссылок по HTTP. Языковые модели, эмбеддинги, кластеризация и распознавание речи в движок не входят. Всё, что требует суждения о смысле текста, делает человек, а сервер собирает для него очередь, предлагает варианты с объяснением и ведёт учёт решений.
Вики наследует анти-цели DV Hub. В ней нет ленты, лайков, счётчиков просмотров для читателя и публичных комментариев. Обсуждение существует в одной форме: замечание проверяющего или редактора, привязанное к конкретному фрагменту текста (раздел 4.3). Открытых страниц обсуждения, как в Википедии, нет, потому что они быстро превращаются в форум.
2 Связь с DV Hub
Новый репозиторий повторяет устройство документации DV Hub: docs/product-vision.md с целями и анти-целями, docs/architecture.md с ADR, docs/roadmap.md, AGENTS.md с конвенциями. Стек тоже совпадает, чтобы команда не учила второй: Node.js 22, Hono, TypeScript strict, better-sqlite3, Tailwind, npm, идемпотентные миграции через schema_migrations по образцу ADR-009 DV Hub.
От конвенций DV Hub вики отступает в двух местах, и оба отступа записываются отдельными ADR в новом репозитории. Первый: страницы рендерятся на сервере, а не в SPA. Вики должна открываться без JavaScript, индексироваться поисковиками и отдаваться из кэша nginx анонимному читателю. Второй: адреса настоящие (/wiki/Экология_(наука)), а не hash-маршруты, потому что на страницу вики ссылаются извне и цитируют её по ревизии.
DV Hub общается с вики через узкий API с токеном. Кнопка «Отправить в вики» на публикации DV Hub вызывает POST /api/v1/import с названием, видом страницы synthesis, Markdown-текстом, списком источников, участниками и ссылкой на тему в DV Hub. Вики создаёт черновик от имени куратора темы, и дальше страница живёт по правилам вики. Материалы DV Hub можно заносить в реестр источников через POST /api/v1/sources. Ссылка на тему DV Hub хранится в поле origin_url как внешний источник происхождения, без знания о таблицах DV Hub. Запись дискуссии или протокол комнаты становится источником вида «запись обсуждения» (раздел 4.2).
Хостинг: тот же VPS Fornex, отдельный процесс PM2 за Nginx на своём поддомене или на корне re-search.wiki (раздел 10). SQLite-файл и бэкапы отдельные от DV Hub.
3 Дерево знаний
3.1 Узлы
Всё содержимое вики лежит в одном реестре страниц, и у каждой страницы есть вид. Видов пять: категория, статья, открытый вопрос, страница значений, перенаправление. Новые справочники рядом с реестром не заводятся: если завтра понадобится список персоналий или глоссарий, это ветка дерева, а не отдельная таблица. Пять параллельных справочников на один вопрос «что это такое» сходятся только в голове у человека, и этого надо избежать с первого дня.
Категория описывает род предметов: «Общественные движения», «Методы исследования». У неё есть необязательный текст-введение и меню из дочерних узлов. Категория без своего текста, но с потомками, остаётся живой и показывает их список. Категория без текста и без потомков читателю не показывается и попадает в служебный список пустых категорий. Статья описывает один предмет и отвечает на один вопрос «что это». Если в название статьи просится место, время или условие («Экология в Нячанге в 2025 году»), это признак, что место и время принадлежат тексту или оси (раздел 3.5), а не названию. Открытый вопрос собирает то, что неизвестно: формулировку, позиции с источниками, ссылки на синтезы DV, где его обсуждали. Такая страница нужна вики о правде не меньше статьи, потому что честное «пока не ясно» тоже знание.
У каждой страницы есть жизненный цикл, отдельный от статуса проверки (раздел 4.4):
- Заявка. Страницы ещё нет, но кто-то её заказал из списка требуемых страниц или из неудачного поиска. Красная ссылка на неё показывает «статья заказана».
- Черновик. Текст есть, страница видна только редакторам.
- Заготовка. Опубликована, но короче порога полноты (по умолчанию 1500 знаков и меньше трёх источников). Это законное состояние: заготовка стоит в очереди редакции, ранжированной спросом (раздел 5.4).
- Опубликована.
- Снята. Запись остаётся с причиной и датой, читатель получает ответ 410 с причиной или перенаправление 301, если страницу слили с другой.
Сама рождается только заявка. Сервер никогда не создаёт страниц с текстом по своему усмотрению, даже если видит спрос: страница без вещества обещает читателю то, чего нет.
3.2 Место в дереве
Каждая статья лежит в дереве один раз: у неё одна основная категория. Основная категория даёт хлебные крошки, родителя в меню, разметку BreadcrumbList для поисковиков и путь в выгрузке. Кроме основной, статья входит в любое число дополнительных категорий, и каждая из них на неё ссылается. «Экология (движение)» лежит в «Общественных движениях» и дополнительно входит в «Исследовательские темы DV». Внизу статьи выводятся все категории, а строка «Также входит в» под заголовком показывает дополнительные с их путями до корня. Категории устроены так же: у категории одна основная родительская категория и сколько угодно дополнительных, граф без циклов. Сервер отвергает правку, которая замыкает цикл, и называет категории, через которые цикл прошёл.
Основную категорию выбирают по роду предмета, а не по слову в названии. Ветка, собранная по слову («Бамбук», куда сложили и растение, и мебель, и курительную трубку), режет дерево поперёк смысла: растение живёт в ботанике, мебель в изделиях, общего у них только слово. Это же правило помогает с одноимёнными понятиями: если два предмета называются одинаково, но принадлежат разным родам, это две статьи с уточнениями и страница значений, а не одна статья в двух ветках.
Тот же предмет может оказаться в дереве дважды по ошибке: два редактора независимо написали статьи под разными названиями. Ночью сервер ищет возможные дубли по трём признакам: общая основа названия или пересечение перенаправлений, высокая доля общих источников, высокая доля общих ссылок. Пары с высоким коэффициентом Жаккара попадают в служебный список «Возможные дубли». Слияние делает человек: инструмент переносит историю обеих страниц в одну, на месте второй оставляет перенаправление 301.
Номер страницы не меняется никогда. Переименование меняет название и адрес, старый адрес отвечает перенаправлением 301, ссылки по номеру в выгрузке и API продолжают работать. Глубина дерева не ограничена, но сервер предупреждает редактора о пути длиннее шести уровней: такая глубина почти всегда значит, что один из уровней смешивает два рода.
Корней может быть несколько. Верхний ярус задают администраторы, и только они создают категории первого и второго уровня. Ниже категории создают редакторы. Так дерево растёт ветками по мере появления статей, а не раскладывается заранее по чужому классификатору на тысячи пустых узлов, каждый из которых портит подбор места и поиск.
3.3 Названия
У понятия четыре формы, и они строго разделены. Идентификатор понятия общий для всех языковых версий. Человеческое название («Экология (наука)») видит читатель, из него строится заголовок и адрес. Нормализованная форма (нижний регистр, ё → е, единые пробелы, основы слов по стеммеру Snowball) идёт в поиск, в проверку единственности и в подсказки связей. Латинская транслитерация используется только в технических строках: именах файлов выгрузки и якорях, где кириллица неудобна. Заголовок никогда не строится из нормализованной формы, а транслитерированная копия названия не может быть отдельной страницей.
Название уникально в пределах языка после нормализации. Это ограничение базы (UNIQUE (lang, title_norm)), а не дисциплина редактора: на сотой странице дисциплину забывают. Две страницы об одном предмете соревнуются за одну поисковую выдачу и проигрывают обе. Регистр, пробелы, знаки препинания и транслитерация дублей не порождают, их ловит нормализация. Перевод дублем не считается: русская и английская статьи об одном понятии связаны идентификатором понятия.
Перенаправление хранится с видом: синоним, другое написание или опечатка, сокращение, множественное число, более узкое понятие. Узкое понятие может вести на раздел статьи ([[Экология (наука)#охрана-природы|Охрана природы]]), как в Википедии. Виды нужны для двух вещей: опечатки не показываются в подсказках поиска, а узкие понятия попадают в служебный список кандидатов на собственную статью, если на них много ссылок.
Страница с одинаковой основой названия образует группу значений. На каждой странице группы под заголовком выводится строка «Другие значения: Экология (движение), Экология (исследовательская тема)», и у каждого значения указана его основная категория, чтобы читатель увидел, к какой области относится соседнее понятие. Если по адресу без уточнения нет статьи, сервер отдаёт автоматическую страницу значений: список статей группы с первым предложением и путём основной категории каждой. Если статья без уточнения существует (основное значение), строка выглядит так: «Об экологии как науке; другие значения см. Экология (значения)». Редактор может переписать страницу значений вручную, тогда сервер дописывает в её конец только значения, которых в ручном тексте нет.
Для списков в меню и на страницах категорий у статьи есть короткая подпись. Если у трёх и больше соседей по категории названия начинаются с одних и тех же двух и более слов («Методы исследования качественные», «Методы исследования количественные»), сервер предлагает подписи без общего начала. Предложение показывается редактору, и только утверждённая подпись выводится в меню, потому что отрезанный остаток бывает в неправильном падеже. Отклонённое предложение запоминается и больше не предлагается.
3.4 Подбор места
При создании статьи сервер предлагает основную категорию. Подбор лексический: слова названия и первого абзаца сравниваются со словами названий категорий, их перенаправлений и статей, которые в них уже лежат, с весом слова, обратным его частоте по всей вики. Ответ всегда ряд из трёх-пяти кандидатов с объяснением: «совпали: исследование, качественный, интервью». Уверенность показывается как отрыв первого кандидата от второго, а не как абсолютное число. Ровный ряд значит «подходящей категории нет», и это законный ответ: редактор создаёт категорию или оставляет статью без неё, и статья попадает в список сирот. Порога, ниже которого сервер молча не предлагает ничего, нет.
Сервер запоминает выбор вместе со списком, из которого выбирали. «Взял третий вариант» и «взял единственный» оставляют разный след, и только по этому следу можно понять, хорошо ли работает подбор. Подтверждённые решения пополняют словарь категорий: слова статей, которые люди положили в категорию, начинают тянуть туда похожие. Векторный подбор сюда не нужен. На подобных задачах лексический поиск по роду с весом по редкости слова попадал чаще эмбеддингов, потому что эмбеддинги ловят тему, а не род, и при этом стоят дорого.
Тот же подбор работает при пустом поиске («похоже, вы ищете в категории…») и при проверке на дубли перед созданием страницы: редактор видит существующие статьи с похожими названиями и перенаправлениями до того, как напишет вторую.
Статья без категории и категория без родителя (кроме корней) попадают в список сирот, и это список работы, а не мусор. Если редакторы ставят ссылку на несуществующую категорию, сервер показывает её в «Требуемых категориях» с числом статей, которые туда просятся. Категории сам сервер не создаёт.
3.5 Язык и место
Языковая версия статьи считается отдельной страницей со своим названием, адресом и ключевым запросом, а связывает версии общий идентификатор понятия. Английская статья не обязана быть переводом русской: англоязычный читатель спрашивает о предмете своими словами. Если версия всё же сделана переводом, она хранит номер ревизии оригинала, с которой переводили. Когда у оригинала появляется новая проверенная ревизия, сервер помечает перевод для редакторов как «перевод отстал на N правок» и показывает разницу оригинала с момента перевода. Это сравнение двух ревизий, вычислений сверх этого не нужно.
Отдельной оси места в дереве нет. Региональная статья («Экология (Вьетнам)», «Закон о … (Вьетнам)») получает уточнение в названии и ложится в ветку географии как дополнительную категорию. Этого хватает, потому что вики описывает понятия, а не предложения в каждом городе. Сетку «понятие × место» сервер не порождает: из каждой пары, которую можно построить, осмысленна малая доля, и пустые страницы лишь обещают то, чего нет. Читателю, которому нужно пересечение, доступна выборка по двум категориям: /wiki/Категория:Экология?и=Категория:Вьетнам покажет статьи, входящие в обе ветки с учётом вложенных категорий. Выборка считается запросом к SQLite, отдельных страниц не создаёт и закрыта от индексации.
3.6 Внешние классификаторы
Своё дерево главное, внешние классификаторы вторичны. Код внешнего классификатора (идентификатор Wikidata, УДК, ГРНТИ) хранится свойством узла и не влияет на его место в дереве. Попытка сделать дерево изоморфным классификатору ломается на втором классификаторе: они не совпадают ни между собой, ни между своими редакциями. Узел без кода остаётся законным узлом.
Связь узла с кодом хранится с видом: то же самое, близкое (узел уже или шире кода), объединение (несколько узлов в один код), разделение (один узел в несколько кодов). Однозначность здесь проверяемое условие: у узла не больше одной связи вида «то же самое» на пару «классификатор, редакция», и сервер отвергает правку, которая это нарушает. Отчёт «узлы без однозначного кода» служит списком работы для того, кто ведёт соответствия.
Первым стоит подключить Wikidata. Идентификатор Wikidata даёт ссылку sameAs в разметке для поисковиков (раздел 5.2), связь с Википедией на других языках и устойчивое различение одноимённых понятий. Подбор идентификатора делается одним запросом к API Wikidata по кнопке редактора, а утверждает его человек.
3.7 Ссылки и навигация
Внутренняя ссылка пишется как [[Название]] или [[Название|текст]]. При сохранении сервер раскладывает все ссылки в таблицу links, включая ссылки на несуществующие страницы. Отсюда получаются красные ссылки, список «Требуемые страницы» с числом ссылающихся статей и раздел «Ссылки сюда» на каждой странице. Ссылка на страницу значений помечается, и такие ссылки попадают в служебный список «Ссылки на неоднозначные названия»: автор почти всегда имел в виду конкретное значение.
Меню страницы (левая колонка) сервер строит из дерева, руками его никто не собирает. У статьи в меню основная категория, соседи по ней, дополнительные категории и языковые версии. У категории дочерние категории и статьи. Если меню статьи пусто, статья сирота, и правильный ответ на это поставить её в категорию, а не повесить ссылки «для связности».
Автоматической вставки ссылок в текст нет: «Экология» в тексте о движении автоматика свяжет с наукой. Вместо этого в предпросмотре правки сервер показывает подсказки «можно связать»: упоминания названий и перенаправлений существующих статей, найденные по основам слов. Редактор отмечает нужные, и сервер ставит ссылку на первое вхождение. Для многозначного названия подсказка спрашивает, какое значение имелось в виду. У ссылки из блока «См. также» есть причина: общая категория, общий источник или взаимная ссылка. Блок сервер собирает ночью по коэффициенту Жаккара, а редактор может закрепить или скрыть пункты.
4 Достоверность
4.1 Разметка утверждений
Текст пишется в Markdown с тремя расширениями. Ссылка на источник выглядит как [@ключ] или [@ключ, с. 14] и ссылается на запись в реестре источников. Список литературы внизу страницы сервер строит сам из этих ссылок. Блок мнения оформляется контейнером ::: мнение автор="...", блок открытого вопроса контейнером ::: вопрос, и оба выводятся с рамкой и подписью, которые нельзя убрать стилем. Блок ::: скрыто виден только редакторам и проверяющим: в нём лежат рабочие заметки и всё, что касается частных лиц. Рендер для читателя, выгрузка и API этот блок вырезают на уровне разбора, а не стилем, так что попасть наружу ему нечем. Метки на фрагменте ставятся в фигурных скобках: {нужен источник}, {спорно#42} со ссылкой на замечание, {на дату 2026-03} для утверждений, которые устаревают.
Сервер размечает текст автоматически при каждом сохранении. Абзац фактического текста без единой ссылки на источник получает пунктирное подчёркивание и подпись «Без источника». Проверяющий может снять метку с абзаца, если тот пересказывает уже подтверждённое ниже или содержит общеизвестное, и это решение записывается в ревизию с его именем. Утверждение с меткой {на дату ...} старше заданного срока (по умолчанию 2 года) получает подпись «Сведения могли устареть». Ссылка на источник с домена из запрещённого списка (раздел 4.2) выводится с предупреждением прямо в тексте. У страницы считается доля абзацев с источниками, и читатель видит её рядом со статусом.
4.2 Источники
Источник заносится в реестр один раз и получает свою страницу /source/<ключ>. Реестр принимает URL, DOI, ISBN, печатное издание без адреса и запись обсуждения DV. По DOI и ISBN метаданные подтягиваются из открытых каталогов (Crossref, Open Library) одним запросом при добавлении, по URL сервер забирает заголовок, автора и дату из HTML-метатегов. Страница источника показывает метаданные, надёжность домена, состояние проверки доступности и все статьи, которые на него ссылаются. Одна мысль, подтверждённая двумя источниками, остаётся одним утверждением с двумя ссылками.
Надёжность задаётся на уровне домена списком, который ведут администраторы, по образцу списка постоянных источников Википедии. Уровней четыре: надёжный, обычный, сомнительный, запрещённый. Новый домен получает уровень «обычный». Ссылка на сомнительный источник показывается проверяющему как требующая внимания, ссылка на запрещённый требует при сохранении явного подтверждения с объяснением. Когда администратор понижает домен, все статьи с его источниками автоматически попадают в очередь перепроверки, а их проверенный статус дополняется пометкой «источник пересмотрен». Сервер находит такие статьи одним запросом по таблице цитирований.
Запись обсуждения (протокол или расшифровка комнаты DV) годится как источник того, что участники сказали, и не годится как источник внешнего факта. Сервер не даёт снять метку «Без источника» с абзаца энциклопедической статьи, если единственный его источник имеет этот вид.
Раз в 30 дней сервер проверяет каждый внешний источник: запрос HEAD, при отказе GET, не больше одного запроса к домену в несколько секунд. Сервер сохраняет код ответа и хеш нормализованного текста страницы. Два ответа 404 или 410 подряд дают читателю метку «Источник недоступен» рядом со ссылкой. Изменение хеша показывается только проверяющим как «источник изменился с момента цитирования». При первом цитировании сервер сохраняет копию текста источника в сжатом виде, доступную только проверяющим: если источник исчезнет или перепишется, у проверяющего остаётся то, на что опирался автор. Публичных копий вики не раздаёт из-за авторских прав, а архивирование через внешние сервисы выключено по умолчанию, потому что раскрывает список источников третьей стороне.
4.3 Проверка ревизий
Каждая правка создаёт полную ревизию. Ревизии не удаляются и не редактируются, разница между ними считается при показе пословным сравнением. Проверяется ревизия, а не страница: так устроены проверенные версии немецкой Википедии, и это даёт читателю стабильный проверенный текст при живом редактировании.
Проверяющего назначает администратор, с указанием категорий, в которых тот проверяет. Свою ревизию проверить нельзя. Проверяющий открывает очередь, видит разницу между последней проверенной ревизией и текущей, а не весь текст, и проходит короткий список: каждый новый источник открывается и подтверждает то утверждение, к которому привязан; мнения размечены как мнения; новые абзацы без источников либо помечены, либо обоснованно освобождены; в открытом тексте нет персональных данных частных лиц. Вердикт один из трёх: подтвердить, вернуть с замечаниями, откатить. Подтверждение допускается с открытыми замечаниями на отдельных фрагментах, тогда эти фрагменты остаются помеченными как спорные.
Замечание привязывается к фрагменту текста (якорь по цитате и смещению) и имеет ветку ответов, причину и состояние. Читать замечания могут все, писать только редакторы и проверяющие. При следующей правке сервер переносит якорь на новую ревизию по совпадению цитаты, а если цитата исчезла, предлагает закрыть замечание.
Читатель тоже может сообщить об ошибке: кнопка у каждого абзаца открывает форму с полем «что не так» и необязательным полем для источника. Сообщение попадает в очередь проверяющих как замечание без автора. Защита от спама держится на ограничении частоты по IP и скрытом поле-ловушке, капча не нужна.
Для категорий, которые администратор отметил как чувствительные (здоровье, право, живые люди), подтверждение требует двух проверяющих. Проверка устаревает через 12 месяцев: страница сохраняет статус, но получает подпись «Последняя проверка: 14.10.2025» и возвращается в очередь с низким приоритетом.
Если проверенный текст оказался ошибочным, правка с отметкой «исправление» попадает в блок «Исправления» внизу страницы: дата, что было неверно, что стало. Этот приём взят из практики новостных редакций.
4.4 Статусы для читателя
Статус проверки сервер вычисляет из ревизий и замечаний, вручную его никто не ставит. Для опубликованной страницы статусов четыре:
- Не проверено. Проверенной ревизии нет. Жёлтая плашка над текстом.
- Проверено. Плашки нет, под заголовком строка «Проверено: Иванова, 14.10.2026, ревизия 312».
- Есть непроверенные правки. Читатель видит проверенную ревизию и ссылку «Показать последнюю версию (3 правки не проверены)».
- Оспаривается. Есть открытые замечания по существу. Оранжевая плашка с числом замечаний и ссылкой на них, спорные фрагменты подсвечены.
Анонимный читатель по умолчанию видит последнюю проверенную ревизию, вошедший редактор видит последнюю. Постоянная ссылка на ревизию (/wiki/Название?rev=312) показывает именно её, со статусом на момент проверки, чтобы цитата из вики не менялась под ногами у того, кто её привёл. Как статус влияет на индексацию, сказано в разделе 5.3.
5 Поисковая оптимизация
5.1 Адреса и ответы
Вики живёт на одном домене с плоскими адресами /wiki/<название>, как Википедия. Поддомен или отдельный домен на каждый запрос близок к дорвею: поисковики наказывают за такую сетку, а авторитет размазывается по сотням хостов вместо одного. Языковые версии получают префикс пути (/en/wiki/...) или поддомен (en.), выбор записывается в ADR. Раскладка адресов задаётся в конфиге, а не зашивается в генератор страниц: сменить префиксы можно без правки данных.
В адресе стоит человеческое название с подчёркиваниями вместо пробелов. Кириллица в пути кодируется браузером, Яндекс и Google показывают её в выдаче читаемой. Транслитерация адресов не нужна и вредна: она создаёт вторую форму названия, которую придётся держать согласованной с первой.
Коды ответа отражают состояние страницы честно. Существующая страница отвечает 200. Перенаправление, переименование и слияние отвечают 301 на канонический адрес, и редактор, пришедший по перенаправлению, видит строку «перенаправлено с …». Несуществующая страница отвечает 404 со строкой поиска, похожими названиями и, для вошедших, кнопкой «создать». Снятая страница отвечает 410 с причиной. Красные ссылки анонимный читатель видит как текст без адреса, чтобы поисковый робот не ходил по сотне адресов с ответом 404.
5.2 Разметка страницы
Тег <title> берётся из поисковой подписи страницы, если редактор её задал, иначе из названия с именем сайта. Мета-описание сервер берёт из первого предложения статьи, обрезая по границе слова до 160 знаков, редактор может его переписать. Канонический адрес указан на каждой странице. Хлебные крошки строятся по основной категории (раздел 3.2) и дублируются разметкой BreadcrumbList.
Текст статьи следует правилам, которые одновременно служат читателю и поисковику. Первое предложение содержит название статьи жирным, как в Википедии, и это единственное место, где жирный шрифт выделяет ключевой запрос: выделять ключевики по всему тексту вики не будет. Заголовок первого уровня на странице один, разделы идут вторым и третьим уровнем без пропусков, у каждого раздела есть якорь. Заголовки выделяются размером, а не жирностью. У каждого изображения есть подпись и альтернативный текст, ширина и высота заданы, загрузка отложенная. Нарушения сервер показывает редактору предупреждениями в предпросмотре, сохранение они не блокируют.
Разметка JSON-LD на каждой статье описывает Article с названием, датой проверенной ревизии в dateModified, списком источников в citation, ссылкой на Wikidata в sameAs (раздел 3.6) и BreadcrumbList. Главная страница несёт WebSite с SearchAction для строки поиска в выдаче. Ссылки между языковыми версиями задаются тегами hreflang с x-default на основной язык. Open Graph берёт название, описание и первое изображение статьи, при отсутствии изображения подставляет картинку сайта.
Скорость входит в оптимизацию напрямую. Страница рендерится на сервере, кэшируется nginx, отдаётся со сжатием, заголовками ETag и Last-Modified и работает без JavaScript. Отдельной работы по ускорению при таком устройстве не нужно.
5.3 Индексация
Поисковики индексируют только то, что вики готова подтвердить. Страница без проверенной ревизии, заготовка и черновик отдаются с noindex. Для страницы с непроверенными правками робот получает проверенную ревизию, как анонимный читатель. История, сравнение, правка, служебные списки, выборки по двум категориям и адреса с ?rev= закрыты noindex и запрещены в robots.txt, а у адресов с ?rev= канонический адрес указывает на основную страницу. Страницы значений индексируются: на однословный запрос они отвечают лучше любой из статей группы. Отключить noindex для непроверенных страниц можно настройкой, но по умолчанию он включён, чтобы непроверенное не разносилось через поисковую выдачу.
Файл sitemap.xml сервер пересобирает ночью из индексируемых страниц, с lastmod по дате проверенной ревизии и разбиением на файлы по 50 000 адресов. Когда страница получает проверенную ревизию, сервер одним HTTP-запросом сообщает адрес по протоколу IndexNow, который принимают Яндекс и Bing. Google узнаёт об изменениях из карты сайта.
5.4 Спрос
Заголовок пишется человеческим языком, а какие темы нужны, подсказывает спрос. Справочники и классификаторы дают структуру, но значительная часть их названий написана языком, которым никто не ищет. Поэтому ни одна статья не заводится только потому, что узел есть в чужом классификаторе.
Спрос сервер собирает из трёх дешёвых источников. Поиск по вики записывает запросы без результата, обезличенно, без IP и без привязки к пользователю. Таблица ссылок даёт красные ссылки с числом ссылающихся статей. Администратор может загрузить выгрузку запросов из Яндекс Вебмастера или Google Search Console в CSV, и сервер сопоставит запросы с названиями и перенаправлениями. Из этого складывается разрыв: что спрашивают и на что у вики нет ответа или есть только заготовка. Список «Что написать» ранжирует заявки, заготовки и красные ссылки по этому разрыву, и редакторы берут работу оттуда.
Запросы, которые совпали с перенаправлением, а не с названием, показывают, что читатель называет предмет иначе, чем вики. Если такой запрос встречается чаще названия, сервер предлагает редактору рассмотреть переименование. Решает человек.
6 Интерфейсы
6.1 Читатель
Страница статьи: хлебные крошки по основной категории, заголовок, плашка вида и статуса, строки «Также входит в» и «Другие значения», оглавление для страниц длиннее трёх разделов, текст с метками, список литературы, «См. также», «Исправления», категории. В левой колонке меню из дерева (раздел 3.7), в правой ссылки «История», «Ссылки сюда», «Цитировать страницу», «Сообщить об ошибке». Цитирование выдаёт готовую ссылку по ГОСТ Р 7.0.100 и в BibTeX на конкретную ревизию.
Поиск: одно поле в шапке. Точное совпадение названия или перенаправления ведёт сразу на статью, иначе открывается выдача. Выдача ранжируется по BM25 из FTS5 с весом для названия, у каждого результата фрагмент с подсветкой, путь основной категории и статус достоверности. При пустой выдаче сервер предлагает близкие названия по триграммам, подходящие категории по подбору места (раздел 3.4) и ссылку «Заказать статью».
Навигация: страница категории с подкатегориями, статьями по алфавиту с короткими подписями, буквенным указателем и счётчиком статей во всём поддереве; выборка по двум категориям; корневой список категорий; случайная проверенная статья. Вёрстка работает на ширине телефона и без JavaScript, печатная версия скрывает служебные элементы и оставляет метки статуса.
6.2 Редактор
Редактор работает в текстовом поле с Markdown и предпросмотром, который рендерит сервер. Визуального редактора нет. Над полем панель вставки: ссылка на страницу с подсказками названий, ссылка на источник через диалог реестра (вставил URL, DOI или ISBN, получил [@ключ]), блоки мнения, вопроса и скрытого текста, метки. Под полем выбор основной и дополнительных категорий с кандидатами подбора места, поисковая подпись и мета-описание. В предпросмотре сервер показывает предупреждения: абзацы без источников, ссылки на неоднозначные названия, источники с сомнительных и запрещённых доменов, нарушения разметки из раздела 5.2, подсказки «можно связать», похожие существующие статьи. Блокируют сохранение только цикл категорий, повтор названия и неподтверждённый запрещённый источник.
Правка может касаться одного раздела: ссылка «править» у каждого заголовка открывает только его текст. Это сокращает конфликты. Конфликт сервер ловит по номеру базовой ревизии: если страницу изменили, пока редактор писал, сохранение не проходит, и редактор видит разницу между своей версией и новой с возможностью перенести изменения вручную. Автоматического слияния нет.
История страницы: список ревизий с автором, датой, описанием правки, изменением размера в байтах и отметкой проверки. Любые две ревизии сравниваются пословно. Откат на выбранную ревизию делается одной кнопкой и сам становится новой ревизией. Инструменты переименования, слияния и снятия доступны со страницы истории и пишут причину в журнал. Список наблюдения показывает правки на отмеченных страницах и отдаётся в Atom, уведомлений по почте и в Telegram нет.
6.3 Проверяющий
Очередь проверки сортируется по приоритету: сначала сообщения читателей и страницы с понижёнными источниками, затем непроверенные правки в чувствительных категориях, затем прочие по давности, в конце устаревшие проверки и отставшие переводы. Фильтр по категориям из полномочий проверяющего стоит по умолчанию. Экран проверки показывает разницу с последней проверенной ревизией, рядом с каждым новым источником его надёжность, код последней проверки доступности и кнопку открыть сохранённую копию. Список проверки из раздела 4.3 выведен флажками, вердикт и комментарий внизу.
6.4 Служебные страницы
Служебные списки сервер пересчитывает при сохранении или ночью, и через них редакторы находят работу без поручений:
- Что написать: заявки, заготовки и красные ссылки по спросу (раздел 5.4).
- Сироты: статьи без категорий, категории без родителя, статьи, на которые никто не ссылается.
- Тупики: статьи без исходящих ссылок.
- Возможные дубли.
- Без источников: статьи с долей абзацев без источников выше половины.
- Ссылки на неоднозначные названия.
- Требуемые категории и пустые категории.
- Недоступные и изменившиеся источники, источники с сомнительных и запрещённых доменов.
- Отставшие переводы.
- Узлы без однозначного кода внешнего классификатора.
- Свежие правки: все правки по времени, с фильтром «только непроверенные».
Свежие правки по форме похожи на ленту, но служат инструментом надзора, видны только вошедшим и в интерфейс читателя не попадают.
6.5 Администратор
Администратор назначает роли и полномочия проверяющих по категориям, ведёт верхние ярусы дерева, список надёжности доменов и чувствительные категории, защищает страницы от правки, блокирует участников, загружает выгрузки поисковых запросов. Каждое из этих действий пишется в журнал с автором, датой и причиной, и журнал открыт всем вошедшим.
7 Работа сервера
При сохранении правки сервер в одной транзакции записывает ревизию, рендерит HTML и кладёт его рядом с исходником, раскладывает ссылки, категории и цитирования по таблицам, проверяет цикл категорий и единственность названия, пересчитывает группу значений, обновляет индекс FTS5 и сбрасывает кэш nginx для страницы и страниц её группы и категорий. Всё это занимает миллисекунды на страницу обычного размера. HTML в базе служит кэшем: его всегда можно пересобрать из исходника, и команда npm run rebuild делает это для всей вики. Чтение страницы сводится к выборке готового HTML и статуса, анонимных читателей обслуживает кэш nginx.
Когда страница получает проверенную ревизию, сервер отправляет её адрес по IndexNow. Ночью сервер пересчитывает «См. также», возможные дубли, служебные списки, спрос и устаревшие проверки, проверяет доступность очередной трети источников по графику, пересобирает sitemap.xml и делает бэкап: VACUUM INTO для SQLite и выгрузку всех страниц в Markdown-файлы с YAML-шапкой в отдельный git-репозиторий. Шапка повторяет подход OKF из context/ DV Hub: поле type (Wiki Article, Wiki Synthesis, Wiki Category), номер, название, основная и дополнительные категории, статус, номер проверенной ревизии. Папки выгрузки повторяют дерево по основным категориям, так что архив читается в Obsidian и без вики и переживает потерю базы.
Фоновые задачи лежат в таблице jobs в той же SQLite и выполняются тем же процессом по таймеру. Отдельный брокер и воркеры не нужны.
8 Устройство
8.1 Стек
Node.js 22, Hono с JSX для серверных шаблонов, TypeScript strict, better-sqlite3 с FTS5 (токенизатор trigram, он есть в SQLite с версии 3.34 и работает с русским без морфологии), markdown-it с плагином контейнеров и своим правилом для [@ключ] и {метка}, jsdiff для сравнения, Snowball-стеммер для нормализации и подсказок, Tailwind. Сырой HTML в Markdown запрещён настройкой html: false, так что санитайзер не нужен. JavaScript на клиенте ограничен предпросмотром, диалогом источника, выбором категорий и переключателями сравнения.
Вход повторяет ADR-008 DV Hub: Telegram Login Widget и ссылка на почту, сессии в HTTP-only cookie и таблице sessions. Анонимной правки нет, регистрация по приглашению или по входу через Telegram с ручным подтверждением администратора.
API отдаёт то же вещество, что и страница, и только проверенное: GET /api/v1/page/<название> возвращает проверенную ревизию с категориями, источниками и статусом. Машинному читателю нельзя отдавать непроверенное: он повторит его и сошлётся на вики.
8.2 Хранение
pages id, ns, kind, lang, concept_id, title, title_norm, qualifier,
base_norm, title_short, title_seo, description, lifecycle,
current_rev, verified_rev, status, protection, wikidata_id,
origin_url, retired_reason, UNIQUE(lang, title_norm)
aliases title_norm, lang, page_id, section, kind
revisions id, page_id, parent_id, author_id, md, html, summary, size,
is_correction, translated_from_rev, created_at
reviews id, rev_id, reviewer_id, verdict, checklist_json, comment, created_at
links from_page, to_title_norm, to_page -- to_page NULL = красная
page_categories page_id, category_id, is_primary -- одна основная у страницы
classifier_codes page_id, classifier, edition, code, relation -- same|close|merge|split
placement_log page_id, candidates_json, chosen_id, user_id, created_at
sources id, key, kind, url, doi, isbn, domain, meta_json,
http_status, text_hash, snapshot_gz, checked_at, fail_count
citations rev_id, source_id, paragraph_idx, locator
domains domain, reliability, note, changed_by, changed_at
remarks id, page_id, rev_id, anchor_quote, anchor_offset, kind,
reason, author_id, state, created_at
remark_replies id, remark_id, author_id, body, created_at
search_misses query_norm, day, count
demand_import query_norm, source, impressions, period
users id, telegram_id, email, name, role, review_categories_json
sessions id, user_id, expires_at
watch user_id, page_id
jobs id, kind, payload_json, run_after, attempts
audit_log id, actor_id, action, target, reason, created_at
pages_fts FTS5(title, aliases, text)
Статус и жизненный цикл страницы хранятся денормализованно в pages и пересчитываются при сохранении, проверке и изменении замечаний, чтобы чтение не делало соединений. Категории и пути до корня считаются рекурсивным CTE в SQLite. При глубине до десяти уровней и тысячах категорий это миллисекунды, отдельная таблица замыкания понадобится, только если замеры покажут обратное.
8.3 Адреса
Читательские адреса: /wiki/<название>, /wiki/<название>?rev=<n>, /wiki/Категория:<название>[?и=Категория:<название>], /source/<ключ>, /search?q=, /special/<список>. Редакторские: /edit/<название>[?section=<n>], /history/<название>, /diff?from=<n>&to=<n>, /review, /review/<rev>. Служебные для поисковиков: /sitemap.xml, /robots.txt, ключ IndexNow. API: GET /api/v1/page/<название>, POST /api/v1/import и POST /api/v1/sources по токену. Формат ошибок и коды ответов совпадают с конвенциями API DV Hub.
9 Этапы
Первый этап даёт рабочую вики с честными статусами и правильной индексацией: страницы с видами и жизненным циклом, ревизии, история, сравнение и откат, основная и дополнительные категории с проверкой циклов, перенаправления с видами, единственность названий, автоматические группы значений, ссылки с красными ссылками и «Ссылки сюда», меню из дерева, поиск, реестр источников и цитирования, разметка мнений, скрытых блоков и меток, проверка ревизий, статусы для читателя, сообщения об ошибках, мета-теги, JSON-LD, канонические адреса, noindex для непроверенного, карта сайта, вход, бэкап с выгрузкой в Markdown. Этого хватает, чтобы опубликовать первые синтезы DV вручную.
Второй этап добавляет автоматику, которая окупается с ростом числа страниц: подбор места с журналом решений, короткие подписи, подсказки связей, «См. также», возможные дубли и слияние, служебные списки, спрос из поиска и выгрузок, IndexNow, ночную проверку источников с копиями, список надёжности доменов с перепроверкой при понижении, правку разделов, список наблюдения с Atom, устаревание проверок и сведений, блок исправлений, API импорта из DV Hub. Признак, что пора: в вики больше сотни статей или больше трёх активных редакторов.
Третий этап делается по запросу: языковые версии с hreflang и отставанием переводов, Wikidata и другие внешние классификаторы, выборка по двум категориям, двойная проверка в чувствительных категориях, уровни защиты страниц, цитирование по ГОСТ, карточки с полями. Карточки отложены сознательно: они требуют отдельного языка шаблонов, а без них статьи пишутся нормально.
10 Открытые вопросы
Какие корни у дерева. По умолчанию спецификация предлагает несколько корней, которые задают администраторы, но состав верхнего яруса решает движение. Если вики описывает только темы исследований DV, хватит одного корня с ветками по темам, а если она претендует на общую энциклопедию, верхний ярус лучше взять готовым, например из разделов ГРНТИ, и растить вниз только по мере появления статей.
Нужна ли основная категория у каждой статьи обязательно. Спецификация требует её для хлебных крошек, выгрузки и разметки поисковиков и считает статью без неё сиротой. Альтернатива, как в Википедии, равноправные категории без основной, проще для редактора, но лишает статью единственного пути и папки в выгрузке.
Какие языки нужны на старте. Если только русский, языковые версии и hreflang уходят в третий этап. Если сразу русский и английский, они переходят в первый, и тогда же решается, префикс пути или поддомен.
Где живёт вики: на корне re-search.wiki, тогда DV Hub уходит на поддомен, или на поддомене вроде wiki.re-search.wiki. Название домена подсказывает первый вариант, и для поисковиков он сильнее, но это решение движения.
Кто первые проверяющие и сколько их нужно. При одном проверяющем правило «свою ревизию проверить нельзя» оставит его собственные правки непроверенными навсегда, поэтому для старта нужно минимум двое.
Публичны ли замечания к фрагментам для анонимного читателя. Открытость повышает доверие к статусу, но противоречит закрытой природе DV Hub. По умолчанию в спецификации замечания видны всем, а писать их могут только вошедшие.