Files
progcode/editorial/agent-rewrites/100.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
18 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 100,
"slug": "editorial-2025-03-field-knowledge-retrieval",
"title": "Когда поиск по базе знаний должен остановиться",
"excerpt": "Высокая похожесть найденного фрагмента не доказывает его актуальность, доступность и пригодность для цитирования. Разбираем stop-условие, проверку источника и безопасный путь для неполного результата.",
"contentHtml": "<p>Поиск по инженерной базе часто ломается не тогда, когда ничего не нашёл. Опаснее другой симптом: система возвращает убедительный фрагмент, а команда принимает его за действующее правило. Документ уже закрыт для текущего пользователя, срок его действия истёк или в нём нет точного места, которое подтверждает вывод. Поиск показывает высокий score, интерфейс показывает ответ, а ошибка обнаруживается позже.</p>\n<p>Цена такой ошибки измеряется не длиной задержки. Старая инструкция может привести к неверной миграции. Закрытый фрагмент может попасть в ответ человеку без права доступа. Неточная цитата превращает предположение в решение на ревью. Исправлять последствия дороже, чем остановить ответ на несколько минут и передать вопрос владельцу источника.</p>\n<h2>Тезис: похожесть не равна доказательству</h2>\n<p>Retrieval должен отвечать на два разных вопроса. Первый: насколько найденный фрагмент похож на запрос. Второй: можно ли использовать его для конкретного ответа. Векторный или текстовый поиск решает только первый вопрос. Он ранжирует результаты. Он не подтверждает дату, права и точный смысл документа.</p>\n<p>Поэтому ответ разрешается продолжить только для записи, которая одновременно проходит три проверки: источник доступен этому запросу, источник свеж по заданному правилу и в нём есть точная цитата с устойчивым anchor. Если хотя бы одно условие не выполнено у всех кандидатов, система возвращает stop. Она не заполняет пробел вероятным пересказом.</p>\n<p>Stop не означает «в базе ничего нет». Он означает «найденное нельзя безопасно использовать». Это важное различие для диагностики. Пользователь должен увидеть безопасную причину и следующий маршрут, но не закрытый текст. Владелец документа должен понять, что обновить или разрешить. Так система сохраняет и полезность, и границу доказательств.</p>\n<h2>Механизм проверки</h2>\n<p>У записи источника должен быть контракт. Минимальный набор полей выглядит так: стабильный идентификатор, версия, URI, anchor, владелец, класс доступа, дата публикации, дата индексации и локальная дата истечения. Поле <code>retrievedAt</code> фиксирует момент, когда поиск увидел запись. Эти даты нельзя сводить к одному timestamp: задержка индекса и срок действия документа описывают разные риски.</p>\n<p>Сначала система получает кандидатов. Затем применяет policy-фильтр. Проверка прав происходит до передачи excerpt в контекст ответа. Проверка свежести зависит от типа вопроса. Для операционного вопроса истёкшая инструкция непригодна. Для исторического вопроса она может быть полезна, но ответ должен назвать версию и дату. В обоих случаях правило задают метаданные и владелец политики, а не score.</p>\n<p>Положительная ветка возвращает source id, version, URI, anchor, retrievedAt и разрешённый фрагмент. Отрицательная ветка возвращает код причины: <code>access-denied</code>, <code>expired</code>, <code>missing-anchor</code> или <code>unknown-policy</code>. Закрытый excerpt не входит в отрицательный результат. Такой контракт не делает источник истинным автоматически. Он не даёт системе скрыть отсутствие доказательства.</p>\n<pre><code>const result = retrieve(query, records, policy);\n\nif (!result.accepted) {\n return {\n status: 'stop',\n code: result.reason,\n sourceId: result.sourceId,\n next: result.ownerAction,\n };\n}\n\nreturn {\n status: 'review',\n claim: draftClaim(query, result.excerpt),\n citation: {\n uri: result.uri,\n version: result.version,\n anchor: result.anchor,\n retrievedAt: result.retrievedAt,\n },\n};</code></pre>\n<p>Это учебный пример. Функции <code>retrieve</code> и <code>draftClaim</code> здесь не подключены к настоящей базе и не подтверждают реальную авторизацию. Пример показывает границу: положительный результат ещё требует ручного сравнения утверждения с источником, а отрицательный результат останавливает дальнейшую обработку.</p>\n<h2>Симптомы, причины и действия</h2>\n<table><caption>Диагностика retrieval-ответа</caption><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td>Высокий score, но документ старый</td><td>Ранжирование не учитывает срок действия</td><td>Сравнить <code>expiresAt</code> и <code>retrievedAt</code></td><td>Остановить ответ и направить к владельцу документа</td></tr><tr><td>Найден закрытый фрагмент</td><td>Поиск и проверка прав разделены или проверка выполнена поздно</td><td>Проверить access decision до передачи excerpt</td><td>Вернуть безопасную причину без текста источника</td></tr><tr><td>Цитата ведёт на страницу без места</td><td>В индексе сохранён URI, но нет устойчивого anchor</td><td>Открыть URI и проверить заголовок, номер раздела или якорь</td><td>Остановить ответ и исправить карточку источника</td></tr><tr><td>Разные даты в индексе и документе</td><td>Не различены публикация, индексация и срок действия</td><td>Сопоставить все даты и владельца каждой</td><td>Исправить policy или обновить индекс</td></tr><tr><td>Система отвечает «ничего нет»</td><td>Reject reasons потерялись после фильтра</td><td>Проверить structured result и журнал решения</td><td>Показать safe reason и следующий маршрут</td></tr></tbody></table>\n<h2>Почему HTTP-дата не решает задачу свежести</h2>\n<p>HTTP-заголовок <code>Last-Modified</code> сообщает дату, когда origin считает выбранное представление изменённым. Это полезный сигнал для индексации и условных запросов. Но он не доказывает, что инструкция всё ещё действует. Правило могло устареть из-за миграции, смены владельца или изменения контекста, даже если файл с тех пор не менялся.</p>\n<p>Практическая политика должна назвать, какая дата управляет ответом. Например, <code>publishedAt</code> отвечает за версию документа, <code>indexedAt</code> показывает задержку конвейера, а <code>expiresAt</code> задаёт допустимый срок для операционных вопросов. Если источник не предоставляет нужные данные, это не повод угадывать. Нужна остановка или явное решение владельца о том, какое ограничение действует.</p>\n<h2>Права доступа проверяются отдельно</h2>\n<p>Доступ к документу нельзя выводить из факта, что поисковый сервис смог его прочитать. Индекс может работать с расширенными правами, а запрос пользователя — с ограниченными. Проверка должна учитывать субъекта, ресурс и цель обращения. Deny должен произойти до того, как закрытый фрагмент попадёт в контекст следующего шага.</p>\n<p>Безопасный stop-ответ содержит только то, что разрешено policy: например, внутренний идентификатор записи, общий код причины и группу владельца. Не следует возвращать закрытый заголовок, соседние предложения или признаки, по которым можно восстановить содержание. Уровень подробности диагностического сообщения — тоже часть модели доступа.</p>\n<figure><img src=\"/assets/editorial/2025/knowledge-retrieval-2025-verification-loop.svg\" alt=\"Схема проверки источника: запрос, кандидаты, проверки доступа и свежести, точная цитата, ручная проверка или остановка\"><figcaption>Остановка происходит внутри цикла: кандидат проходит проверку доступа, свежести и anchor до формирования ответа.</figcaption></figure>\n<h2>Порядок действий для одной базы</h2>\n<ol><li>Разделите вопросы на операционные и исторические. Зафиксируйте разные правила свежести, если они действительно различаются.</li><li>Опишите карточку источника. Сохраните id, версию, URI, anchor, владельца, класс доступа и нужные даты.</li><li>Определите обязательные условия ответа: доступ, свежесть и точная цитата. Назовите stop-коды для каждого нарушения.</li><li>Проверьте отрицательный путь. Подложите кандидата с высоким score, но с истёкшим сроком или запрещённым доступом.</li><li>Убедитесь, что excerpt закрытого кандидата не покидает слой проверки. В результате должны остаться только разрешённая причина и маршрут.</li><li>Проверьте положительный путь вручную. Откройте exact source, сравните claim с anchor, подтвердите версию и сохраните citation.</li><li>Назначьте владельца остаточного риска. Если policy неизвестна, вопрос должен попасть к владельцу policy, а не к слою ответа.</li></ol>\n<h2>Отрицательный путь важнее красивого ответа</h2>\n<p>Качество retrieval видно не только по найденным документам. Оно видно по тому, как система ведёт себя при конфликте. Если документ похож на запрос, но истёк, она должна сохранить причину и остановиться. Если фрагмент разрешён, но anchor отсутствует, она не должна ссылаться на всю страницу. Если policy неизвестна, она не должна превращать неизвестность в allow.</p>\n<p>Простой тест проверяет именно это поведение: highest-score candidate получает значение <code>expired</code> или <code>access-denied</code>; итоговый объект имеет статус <code>stop</code>; поля <code>claim</code> и <code>citation.excerpt</code> отсутствуют; присутствуют причина и действие владельца. Это проверяет контракт учебного модуля, но не доказывает работу вашей production-базы. Для реальной системы нужны отдельные проверки identity, источника и наблюдаемости.</p>\n<h2>Ограничения</h2>\n<p>Эта модель не выбирает лучший алгоритм поиска и не обещает, что keyword search, embeddings или reranking дадут конкретную точность. Она не заменяет классификацию документов, аудит прав и управление версиями. Score остаётся полезным для порядка кандидатов, но не становится доказательством. На качество ответа также влияет полнота корпуса: если нужного документа нет, фильтр не создаст его.</p>\n<p>Учебный код не читает настоящую wiki, не обращается к сети и не выполняет реальную authorization decision. Все записи в примере условны. Нельзя переносить его значения дат, score или статусы в production. Переносить стоит только форму результата: accepted либо stop, структурированную причину, citation с anchor и явного владельца следующего действия.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Работа готова, когда для одного выбранного типа вопроса система может показать полный путь. Разрешённый свежий источник даёт версию, URI, anchor и дату retrieval, после чего человек подтверждает claim. Просроченный, закрытый или нецитируемый кандидат даёт stop без раскрытия запрещённого текста. В обоих случаях есть воспроизводимая проверка и назначенный владелец.</p>\n<p>Если тест с высоким score и истёкшим документом всё ещё формирует ответ, проблема находится не в формулировке prompt. Не хватает контракта между индексом, policy и слоем ответа. Сначала добавьте metadata и отрицательную ветку. Только после этого имеет смысл настраивать ranking.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://www.rfc-editor.org/rfc/rfc9110.html#section-10.2.2\" target=\"_blank\" rel=\"noopener\">RFC 9110: HTTP Semantics, Last-Modified</a> — определение HTTP-даты изменения представления.</li><li><a href=\"https://csrc.nist.gov/pubs/sp/800/207/final\" target=\"_blank\" rel=\"noopener\">NIST SP 800-207: Zero Trust Architecture</a> — разделение authentication и authorization перед доступом к ресурсу.</li><li><a href=\"https://docs.opensearch.org/latest/im-plugin/similarity/\" target=\"_blank\" rel=\"noopener\">OpenSearch Documentation: Similarity</a> — score как мера релевантности и порядок результатов, а не проверка фактов.</li></ul>"
}