Свежесть и дрейф: как индекс не даёт себе врать
Иммутабельные ревизии, append-only журнал расхождений, сторож контрольных сумм, метки stale и архивы «что изменилось в знаниях» — главная гарантия RAGRAF.
Проблема, которую решает механика #
Индекс — это кэш. Кэш, который устарел молча, хуже отсутствия кэша: агент принимает решения по фактам о состоянии кода, которого уже нет. Классический сценарий провала:
- Индекс построили в понедельник.
- Во вторник разработчик переименовал функцию и удалил таблицу.
- В среду агент спрашивает «кто вызывает старую функцию» — получает честный, доказанный, устаревший ответ и строит на нём правку.
RAGRAF не обещает «индекс всегда свежий» — это дорого и недостижимо при активной работе. Он обещает другое: устаревание всегда видимо. Ответ либо подтверждает свежесть, либо перечисляет, что разошлось, либо отказывается отвечать. Молчаливой выдачи прошлого за настоящее в архитектуре нет (анти-требование №1, принцип P2).
Четыре механизма #
1. Иммутабельная ревизия #
Индекс строится снапшотом: corpus_hash = sha1(отсортированный список (path, sha256)) фиксирует точное состояние корпуса. Внутри ревизии факты не правятся и не дописываются — только новая ревизия. Следствие: любой ответ воспроизводим — index_rev в конверте однозначно определяет набор фактов.
2. Append-only журнал расхождений (drift ledger) #
Пока агент работает, код правится. Каждая правка регистрируется журналом, а не индексом:
| Поле | Назначение |
|---|---|
entry_id, rev | связь с ревизией, по которой живёт журнал |
change | create / modify / delete — что случилось с файлом |
path, detected_by, detected_at | где, кем замечено (guard / явный вызов), когда |
old_sha256, new_sha256 | точные версии содержимого |
affected_nodes, affected_symbols | какие узлы и символы затронуты |
dedupe_key (UNIQUE) | повторная регистрация того же события не создаёт дублей |
Журнал — единственное место, где живут изменения между ревизиями. Он не удаляется и не перезаписывается: история расхождений — часть аудиторского следа продукта.
3. Сторож контрольных сумм (guard) #
Дешёвая проверка, встроенная в каждый запрос: corpus_hash пересчитывается (или сверяется по кэшу состояния) и сравнивается с ревизией индекса. Расхождение → журнал дополняется, а конверт ответа получает:
"drift": { "files": 3, "symbols": 7, "entries": 4 },
"stale_warnings": [
{ "path": "src/Pipeline/PipelineEngine.php",
"reason": "modify после rev-0007",
"suggest": "live_read" }
]Политика действия настраивается (index.reindex_on_drift): warn (пометить и отдать), auto (переиндексировать прозрачно), fail (отказать — для строгих конвейеров). Факты, затронутые журналом, помечаются stale в самих результатах — устаревшее нельзя перепутать с отсутствующим: это разные ответы.
4. Архив журнала и отчёт «что изменилось в знаниях» #
При переиндексации журнал не исчезает бесследно — он архивируется в drift_archive вместе с ревизией, которая его закрыла, и превращается в человекочитаемый отчёт: какие файлы разошлись, какие символы были затронуты, что индекс «узнал заново». Это редкая для RAG-систем возможность: ответить на вопрос «что изменилось в знаниях о коде между понедельником и средой» — не по git-истории, а по истории фактов. Дедупликация и архивирование вместо бесконечного накопления — прямой вывод из AdaRepair-Mem («больше памяти ≠ лучше»).
Как это выглядит в ответах #
Три честных состояния вместо одного «ответа»:
| Состояние | Что видит агент | Пример |
|---|---|---|
| Свежо | drift.files = 0, предупреждений нет | обычный ответ |
| Разошлось | stale_warnings со suggest + метки stale у затронутых фактов | «функция найдена, но её файл правился после ревизии — перечитай live_read» |
| Невозможно | отказ с причиной (код 3) | журнал противоречит состоянию, требуемая операция несовместима с режимом |
suggest обязателен: предупреждение без инструкции — половина пользы. Варианты: live_read (прочитать файл напрямую — всегда доступно), reindex (пересобрать ревизию).
Проверено протоколами, а не обещанием #
Механика защищена приёмочными тестами, зафиксированными в docs/architecture.md §8.5:
- M5-T2 — журнал не теряет события при параллельных правках одного файла:
dedupe_keyи порядок регистрации проверены сценарием. - M5-T3 — устаревший факт не может быть выдан за свежий ни одной операцией: сквозная проверка по всем операциям, читающим граф.
- M5-T4 — архив журнала восстанавливает полную картину расхождений закрытой ревизии.
- M6 — 100% фактов о коде имеют проверяемые
path:line(скрипт верификации ссылается на реально извлечённые артефакты).
Границы механики #
Честность требует назвать, чего механика не делает:
- Не отслеживает изменения внутри ревизии автоматически в реальном времени — guard срабатывает на запросе или по команде; файловый вотчер не поставляется (перспектива, см. future).
- Не чинит CPG-слой по дельте — Joern не имеет инкрементальной пересборки; после правок, затрагивающих межпроцедурные связи,
sliceчестнее пересобрать целиком. corpus_hash— хеш списка файлов, а не дерева каталогов — переименование каталога без изменения файлов даёт тот же хеш содержимого файлов, но разные пути; guard сравнивает и пути.- Не защищает от правок самого
.ragraf/— файлы индекса лежат рядом с кодом; их целостность — ответственность файловой системы и владельца (поэтому.ragraf/добавляется в.gitignore, а не в репозиторий).
Почему это WOW #
Сравните с обычным RAG: чанки переиндексируются «когда-нибудь», поиск возвращает фрагменты без понятия возраста, и ни один ответ не говорит «я основан на вчерашнем коде». RAGRAF делает свежесть частью контракта каждого ответа — drift, stale_warnings, index_rev и capability присутствуют всегда, даже когда всё хорошо. Агент (и человек, читающий его отчёт) всегда знает, насколько можно доверять факту. Это и есть разница между «поиском по коду» и базой знаний.
Дальше: справочник 22 операций — что спрашивать и как читать ответы →