DarkRiDDeR13 мин

Когда поиск обязан остановиться: нет цитаты — нет ответа

AIДокументация

Самый неприятный результат поиска по инженерной базе — не пустой список. Хуже, когда retriever возвращает точное, но закрытое исключение, или старую инструкцию с отличным semantic match. Если скрыть это за уверенным ответом, ошибку трудно заметить: человек не видит, что цитата отсутствует, а change уже попал в review. Цена ошибки выше, чем задержка: неверная миграция, раскрытие ограниченного материала или спор о том, откуда взялось правило.

В такой ситуации система должна уметь сказать «стоп» до генерации текста. Это не отказ от помощи и не пустая формальность. Stop path превращает неизвестность в конкретный запрос: нужен источник с другим access scope, свежая редакция, точный anchor или owner, который подтвердит policy. Взрослый процесс не заставляет retriever угадывать, какой компромисс допустим. Он показывает, что найдено, что отброшено, почему отброшено и кто может снять ограничение.

Stop condition — часть контракта ответа

У ответа есть положительные и отрицательные условия. Положительное: есть candidate, который разрешён, свеж и содержит exact citation. Отрицательное: хотя бы одно обязательное свойство отсутствует у всех candidates. Тогда system не пишет «вероятно», не делает summary из закрытого snippet и не подменяет stale rule свежей интерпретацией. Она возвращает code stop-no-fresh-authorized-citable-source, query и reject reasons. Это уже достаточный артефакт для следующего человека.

Важная деталь: stop не означает, что corpus пуст. В fixed stale case corpus содержит record с score 0.96; в closed case — record с 0.98. Оба фрагмента подходят по словам, но не проходят другой contract. Если UI показывает только «0 results», он теряет диагностическую ценность. Если UI показывает закрытый excerpt, он нарушает rights. Нужен третий вариант: показать безопасный reason без текста, который нельзя раскрывать, и route для human review.

Решение после retrieval
Состояние candidateЧто можно показать requesterОтвет на вопросСледующий владелец
fresh + allowed + exact citationversion, timestamp, URI#anchor, permitted excerptdraft только после human verificationreviewer и owner source
expiredrecordId, sourceVersion, expiry и reason без ложной актуальностиstopowner documentation или index freshness policy
closedrecordId и access reason без restricted excerptstopowner access policy
no anchorrecordId и missing-anchor reasonstopowner source structure или ingestion pipeline
unknown policyне притворяться allow или denystop / clarifyназначенный owner policy
Вертикальная схема human verification: вопрос, retrieval candidates, проверки прав и срока, открытие exact fragment, сохранение answer с citation. Красная ветка ведёт к STOP при отсутствии права, срока или anchor.
Stop расположен внутри рабочего цикла, а не в конце как аварийная ветка. Благодаря этому rejected candidate оставляет диагностическую причину, но не превращается в недоказанный ответ.

Честная остановка дешевле скрытой догадки

Польза ответа не равна числу слов. Closed source нельзя пересказывать, expired document нельзя назвать текущим. Stop path удерживает спор у границы evidence: query, record id, version, reject reason и requested action. Вместо «модель сомневается» команда получает проверяемое «expired at retrieval time» или «access label not granted» и направляет его владельцу.

Минимальный воспроизводимый пример

import {
  createFixedSyntheticKnowledgeRetrievalInput,
  inspectFixedSyntheticKnowledgeRetrieval,
  prepareSyntheticKnowledgeHumanReview,
} from './upgrade-2025-03.mjs';

const stale = inspectFixedSyntheticKnowledgeRetrieval(
  createFixedSyntheticKnowledgeRetrievalInput('fixed-stale-only-v1'),
);
const review = prepareSyntheticKnowledgeHumanReview(stale);

console.log({
  answerAllowed: stale.accepted,
  stop: stale.decision.code,
  reason: stale.retrieval.candidateOutput[0].reasons[0],
  reviewStatus: review.status,
});

// Expected: false, stop-no-fresh-authorized-citable-source,
// expired-at-fixed-retrieval-time, stopped-before-answer.
// The model is fixed synthetic in memory. It performs no external read or side effect.

Этот example не спрашивает настоящую wiki и не пытается определить, имеет ли реальный человек доступ. Он использует record, timestamp и expiry, жёстко записанные в JS module. Поэтому его можно запускать как fixture: вход имеет строгую shape, report совпадает только с canonical fixed case, extra telemetry field отвергается, forged date не проходит сравнение, cyclic input и report возвращают reject вместо исключения. Такая строгость не делает пример production system, но не позволяет тесту незаметно превратиться в генератор произвольных ответов.

Accepted branch не менее важна. Fresh allowed case формирует citation и status requires-human-verification. Он всё ещё не выдаёт готовый fact. Отдельная функция создаёт только in-memory review draft с шагами: открыть source outside model, сравнить claim с exact anchor, подтвердить access и freshness policy с owner, сохранить answer вместе с citation. Если draft надо остановить, stopSyntheticKnowledgeAnswer удаляет только этот in-memory artefact и явно не пытается менять production, source или document.

Сравнить варианты до того, как спор уйдёт в prompt

Три реакции на непроверяемый candidate
ВариантКраткосрочная выгодаЦена ошибкиРешение автора
Сгенерировать ответ по top-1быстро выглядит полезнымstale или closed rule превращается в implementation decisionне применять: score не является evidence
Скрыть candidate и ответить «ничего нет»не раскрывает fragmentтеряется причина и owner не понимает, что исправлятьпоказать safe reject reason без закрытого excerpt
Остановить answer с structured reasonнужен следующий человек или источникесть задержка и явный ownershipприменять: это сохраняет access и freshness boundary

Human verification проверяет claim, а не доверяет pipeline

После положительного filter остаётся последний переход, который нельзя выкинуть: человек открывает exact source и сравнивает его с proposed claim. Нужны четыре коротких вопроса. Тот ли это version? Есть ли в anchor именно это условие, а не похожая рекомендация? Не потерялось ли ограничение из соседнего абзаца? Разрешает ли policy использовать source в этом контексте? Если любой ответ отрицательный или неизвестный, candidate возвращается в stop path.

Retrieval уже сократил очередь до нескольких candidates, но final verification нельзя заменить confidence: связный текст может опираться на неверный fragment. В fixture human verification остаётся status, а не true; функция не открывает source и не принимает policy. Поэтому automated report не выдан за утверждение о реальном документе.

Карточка human verification
ПолеЧто записатьЧто считается stop
Claimодно проверяемое предложение, не summary всей темыclaim шире, чем exact fragment
CitationURI, anchor, sourceVersion, retrievedAtлюбое поле отсутствует или ведёт не к той редакции
FreshnesspublishedAt, expiry и policy reasonrecord expired или period не определён
Accessразрешённый source class без раскрытия секретной policylabel не допускает reader или purpose
Decisionaccept с limitation либо stop с reasonнет named owner для остаточного риска

Откуда берётся дата и почему её нельзя переоценивать

RFC 9110 даёт полезную, но ограниченную модель Last-Modified: origin server сообщает время, в которое он считает selected representation изменённым. Это подходящий input для indexer, если source отдаёт header. Но из него не следует, что документ не утратил применимость, что все составные части страницы синхронизированы или что previous cached copy идентична current source. Поэтому рабочая карточка содержит несколько дат, а policy прямо называет, какое правило использует answer.

publishedAt отвечает на historical boundary, indexedAt — на lag pipeline, expiresAt — на local rule, retrievedAt — на срез answer. Эти поля отделяют ошибку document owner от ошибки indexer и не позволяют лечить retriever там, где нужно обновить source.

Access check не должен раскрывать закрытый source

NIST SP 800-207 отдельно выделяет authentication и authorization до доступа к enterprise resource. В поисковой системе из этого не следует, что labels из example достаточны для enterprise policy. Следствие уже практическое: decision deny должен произойти до того, как excerpt станет частью answer context. Иначе retrieval формально откажет после того, как закрытый текст уже обработан downstream component или увиден пользователем. В real integration scope и порядок enforcement определяет security owner, а не автор статьи.

В fixed closed case restricted excerpt не выводится. Report содержит record id, version, timestamp, required labels и access-label-not-granted. Это не реальная authorization decision, но интерфейс уже сохраняет нужную границу: причина доступна для диагностики, а текст остаётся закрытым. Уровень раскрытия определяет policy, не layer генерации.

Практический маршрут для одной базы

  1. Выберите один question type. Разделите operational и historical вопросы; у них могут быть разные freshness rules.
  2. Опишите source record. Добавьте stable id, version, owner, даты, access class, URI и exact anchor до настройки ranker.
  3. Сделайте reject reasons явными. Минимум: denied, expired, not-yet-published, missing-anchor и unknown-policy.
  4. Проведите один denial test. Высокий score закрытого или expired record не должен попасть в citation output.
  5. Назначьте human review. Reviewer открывает разрешённый source и подтверждает claim, а не доверяет составленному answer.
  6. Зафиксируйте stop owner. Вопрос без acceptable citation идёт к documentation, access или index owner с короткой карточкой причины.

Ограничения и следующий проверяемый шаг

Полевая статья не описывает реальный incident, не предоставляет реальные правила доступа и не измеряет время ответа. Все score, names, URI, dates, excerpts, allow/deny decisions и outputs принадлежат fixed synthetic in-memory model. Она не читает файлы, Git, сеть, CI, production, часы или telemetry; не извлекает документы и не определяет identity. Поэтому её fixture проверяет только shape и negative branches кода. Она не может подтвердить, что конкретная организация соблюдает access policy или что конкретная база свежа.

Следующий шаг: возьмите один вопрос, на который ваша система сегодня отвечает без citation. Добавьте structured result с accepted, reason, retrievedAt и source card. Затем составьте один test, где highest-score candidate expired или denied. Ожидаемый результат — stop before answer и короткий маршрут к owner. Если этот test нельзя написать из-за отсутствующих metadata, это уже полезный результат: сначала исправьте corpus contract, затем улучшайте модель поиска.

Историческая граница марта 2025

Для этой статьи OpenSearch используется только как подтверждение mechanics similarity search в immutable commit от 12 июня 2024. RFC 9110 используется только для смысла Last-Modified, а NIST SP 800-207 — только для принципа явной authorization boundary. Все три источника существовали до марта 2025 и были закреплены versioned release, RFC либо dated final publication. Stop policy, fields fixed model и human-review sequence — собственная инженерная конструкция автора; она не выдана за обязательную норму этих документов.

Проверяемые источники

  • OpenSearch k-NN README, tag 2.15.0.0, immutable commit 150c589 (12 June 2024) — Официальный README описывает k-NN как nearest-neighbor similarity search по документам и измерениям, а также возможность уточнять similarity search фильтрами. Граница: Источник описывает поиск похожих кандидатов и фильтры. Он не говорит, что score подтверждает истинность утверждения, актуальность документа, право пользователя читать фрагмент или полноту корпуса.
  • RFC 9110: HTTP Semantics, section 8.8.2 Last-Modified (June 2022) — Last-Modified сообщает время, в которое origin server считает выбранное представление изменённым; способ определения значения остаётся implementation detail. Граница: Это метаданные представления HTTP, а не доказательство семантической правильности текста, владельца документа, прав читателя или отсутствия более нового источника.
  • NIST SP 800-207: Zero Trust Architecture (August 2020) — NIST описывает отсутствие неявного доверия по сетевому положению и отдельные authentication/authorization functions до установления сессии к enterprise resource. Граница: Публикация не задаёт схему vector index, формат цитаты или policy свежести инженерной документации. Она поддерживает только принцип явного контроля доступа к ресурсу.