Retrieval (поиск и права) принадлежит Knowledge Store; Query Engine — RAG-слой поверх. Страница держит стык: где кончается хранилище и начинается RAG — граница вызова; как личность уходит вниз как вход фильтра; какой сигнал обращений хранилище читает для staleness; и где живая проверка прав достраивает pre-filter в v2. Узел реципрокности модуля: сюда сходятся входящие ссылки KS, Auth и Harvester.
Когда chat-модель зовёт search_knowledge, Query Engine
вызывает у Knowledge Store
собранный hybrid-результат
одним запросом — это и есть тело инструмента. Четыре примитива поиска
(vector · lexical · graph · sql), их слияние (fusion / RRF) и ACL
pre-filter живут в KS целиком; QE их не повторяет и не собирает
кросс-сторово. Контракт узкий: вниз уходит запрос с личностью, вверх
приходит готовый список кандидатов под правами — дальше начинается RAG.
Граница не пересекается при пустой базе. Если Knowledge Store пуст
(is_empty), search_knowledge модели не
подан — звать нечего, вызов вниз не идёт, и QE просто возвращает ответ
модели из общих знаний.
Query Engine передаёт вниз личность пользователя — email и его маппинг на source-аккаунты. Knowledge Store разворачивает её в резолв-джойн и фильтрует кандидатов одним SQL до ранжирования.
Источник прав — у других модулей, QE их лишь предъявляет. Harvester захватывает права и личности из источников и пишет в KS; их разворот в доступные сущности — резолюция KS. Платформенную же сторону доступа держит Auth & Security — слой «ACL данных»: в v1 Hybrid, где доступ определяет сам источник; платформенная интерпретация поверх source-прав — v2.
Пометка о скрытом — content-free. Pre-filter убирает
запретное насовсем: в списке под правами скрытого нет, и «сколько
убрали» из него не видно. Поэтому рядом с кандидатами хранилище отдаёт
лёгкую пометку об отфильтрованном-но-релевантном — был ли
скрытый кандидат, в какой системе-источнике и кто его автор
(author_principal_id, резолвится в email), без заголовка и
содержимого. На ней стоит
подсказка о
скрытом доступе: автор-контакт «к кому идти» есть в v1; формальный
грантор «кто выдаёт доступ» потребует нового поля в модели прав —
v2.
Аргументы модели — недоверенный ввод.
search_knowledge принимает запрос и фильтры как
значения, не как SQL, а identity — из сессии, не из аргументов
модели. Поэтому prompt-инъекция бессильна: чужого доступа модели не
даст (права — не её аргумент), SQL-инъекцию не построит (значение не
станет кодом — это
забота retrieval-слоя). text-to-SQL не даём осознанно — он открыл бы оба вектора.
Что и как часто запрашивают — видит только Query Engine. Этот сигнал
нужен
staleness-decay
Knowledge Store как вход «частота обращений»: востребованное держит вес,
забытое опускается. Носителя в модели KS нет намеренно — единственный,
кто видит обращения, этот модуль; он и держит счётчик
access_counter
(hits, last_accessed_at на сущность). Сам
decay и его формула — у Knowledge Store.
«Обращение» ≠ сырой retrieve. Засчитываем, когда
сущность реально попала в ответ или по её источнику кликнули. Сырой
retrieve шумен: попадание в кандидаты ещё не польза, и считать его —
подкреплять то, что лишь однажды всплыло. Каждое засчитанное обращение —
дешёвый upsert в access_counter (hits++,
last_accessed_at); кросс-модульного вызова на горячем пути
ответа нет.
Сигнал не отправляется — читается при пересчёте.
Decay — это абсолютный пересчёт trust_score в
Curation Pass
из текущих входов. Спрос он берёт так же, как авторитет: JOIN'ом к
таблице-носителю в момент прогона — authority_tier из
Harvester, частота обращений из access_counter Query
Engine. Отдельного расписания «отправки» нет: каденс сигнала = каденс
Curation Pass, горячий путь ответа и холодный путь доводки развязаны.
Давно не запрашиваемое (last_accessed_at давний) затухает
к нейтрали — забытое опускается само, без удаления.
Pre-filter по сохранённому ACL отвечает быстро, но кэш отстаёт от источника: отозванный доступ может ещё висеть в нашей базе — Harvester синхронизирует права eventually. Чтобы устаревший отзыв не утёк в ответ, для финальных top-N кандидатов делаем живую проверку прав у источника (late-binding security trim): подтверждаем доступ перед самым показом.
Источник не ответил — закрываем (fail-closed). Если в момент проверки источник недоступен (сбой, таймаут), кандидата не показываем: неподтверждённый доступ трактуем как запрет — то же fail-safe-умолчание, что и во всём слое доступа.