# План доработки БД для хранения контекста Story и метаданных RAG ## Цель Зафиксировать проект миграции, который: - добавляет в таблицу чанков признаки артефакта (тип, источник, контекст), - вводит отдельный контур хранения инкремента по `story_id`, - не зависит от выбранного режима RAG (общий/сессионный/гибридный). ## Границы - Документ описывает план и целевую схему. - Реализация SQL-миграций и backfill выполняется отдельным шагом после согласования. ## 1) Метаданные чанков (RAG-слой) ### 1.1. Что добавить Для таблицы `rag_chunks` (или эквивалента таблицы чанков) добавить поля: - `artifact_type` (`REQ|ARCH|API|DB|UI|CODE|OTHER`) - `path` (нормализованный относительный путь файла) - `section` (заголовок/логический раздел документа) - `doc_id` (стабильный идентификатор документа) - `doc_version` (версия документа/ревизия) - `owner` (ответственная команда/человек) - `system_component` (система/подсистема/компонент) - `last_modified` (время последнего изменения источника) - `staleness_score` (0..1, в первую очередь для `CODE`) ### 1.2. Ограничения и индексы - `CHECK` для `artifact_type` и диапазона `staleness_score`. - Индексы: - `(artifact_type)` - `(doc_id, doc_version)` - `(system_component)` - `(path)` - GIN/BTREE по потребности для фильтрации в retrieval. ## 2) Контур Story (отдельно от чанков) ### 2.1. Таблица `story_records` Карточка Story: - `story_id` (PK, строковый уникальный идентификатор) - `project_id` (идентификатор проекта/репозитория) - `title` - `status` (`draft|in_progress|review|done|archived`) - `baseline_commit_sha` (базовый снимок) - `snapshot_id` (опционально для session-RAG) - `created_at`, `updated_at` - `created_by`, `updated_by` Индексы: - `(project_id)` - `(status)` - `(updated_at)` ### 2.2. Таблица `story_artifacts` Связь Story с артефактами изменений: - `id` (PK) - `story_id` (FK -> `story_records.story_id`) - `artifact_role` (`requirement|analysis|doc_change|test_model|note|decision|risk`) - `doc_id` - `doc_version` - `path` - `section` - `chunk_id` (nullable; ссылка на chunk если стабильно поддерживается) - `change_type` (`added|updated|removed|linked`) - `summary` (краткое описание изменения) - `source_ref` (ссылка/внешний id) - `created_at` - `created_by` Уникальность (черновик): - `UNIQUE(story_id, artifact_role, COALESCE(doc_id,''), COALESCE(path,''), COALESCE(section,''), COALESCE(change_type,''))` Индексы: - `(story_id, artifact_role)` - `(story_id, change_type)` - `(doc_id, doc_version)` - `(path)` ### 2.3. Таблица `story_links` Связи Story с внешними сущностями и Story-to-Story: - `id` (PK) - `story_id` (FK) - `link_type` (`story|adr|ticket|pr|commit|doc|external`) - `target_ref` (идентификатор/ссылка) - `description` - `created_at` Индексы: - `(story_id, link_type)` - `(target_ref)` ## 3) Почему `story_id` не в чанках - Один чанк может относиться к нескольким Story. - Чанки нестабильны при переиндексации. - Разделение слоев упрощает поддержку и не привязывает модель к типу RAG. Итог: связь Story и чанков/документов хранить в `story_artifacts`, а не в `rag_chunks`. ## 4) Целевая модель RAG: Hybrid-Lite Выбранный вектор на текущем этапе: `Session-first + Shared Cache + Story Ledger`. ### 4.1. Принципы - Рабочий retrieval выполняется из сессионного индекса (видит незакоммиченные изменения). - Общий кэш чанков/эмбеддингов используется только для ускорения индексации. - Источник правды по инкременту Story находится в Story-таблицах, а не в RAG-индексе. ### 4.2. Что хранить дополнительно - `rag_blob_cache`: кэш файловых blob по `repo_id + blob_sha`. - `rag_chunk_cache`: кэш чанков/эмбеддингов, привязанный к `blob_sha`. - `rag_session_chunk_map`: привязка сессии к используемым chunk (чтобы retrieval был изолированным). - `session_artifacts`: временные артефакты сессии до появления `story_id` (late binding). ### 4.3. Алгоритм индексации (delta-only) 1. На старте сессии сканировать рабочее дерево и считать `blob_sha` для файлов индексации. 2. Для каждого файла: - `cache hit`: взять chunk/embedding из кэша и связать с текущей сессией. - `cache miss`: выполнить chunk+embed и записать результат в кэш. 3. Для retrieval использовать `rag_session_chunk_map` как первичный источник. 4. При необходимости делать fallback к cache-scoped данным по `repo_id` (опционально, под флагом). ### 4.4. Почему это подходит - Нет необходимости в сложном ACL общего RAG на уровне приложения. - Нет обязательной зависимости от ручного commit, индекс отражает локальные изменения. - Снижается время загрузки сессии за счет переиспользования эмбеддингов. - История Story не теряется и не зависит от режима RAG. ### 4.5. Late binding `story_id` (целевой процесс) 1. Аналитик запускает работу только со ссылкой на документ (без `story_id`). 2. Агент обрабатывает задачу в `session-RAG` и сохраняет все изменения в `session_artifacts`. 3. Аналитик вручную делает commit и указывает `story_id`. 4. Вебхук на commit: - извлекает `story_id` из commit metadata/message, - обновляет репозиторный RAG, - выполняет `bind session -> story`: переносит/привязывает `session_artifacts` к `story_artifacts`, - фиксирует связь `story_id <-> commit_sha <-> changed_files`. 5. Исходный документ аналитики тоже попадает в контекст Story ретроспективно, даже если изначально был без `story_id`. ## 5) Черновик DDL (PostgreSQL) ```sql -- 0. Enum-like checks можно заменить на справочники при необходимости -- A) Session artifacts (временный слой до появления story_id) CREATE TABLE IF NOT EXISTS session_artifacts ( id BIGSERIAL PRIMARY KEY, session_id TEXT NOT NULL, project_id TEXT NOT NULL, artifact_role TEXT NOT NULL, source_ref TEXT, doc_id TEXT, doc_version TEXT, path TEXT, section TEXT, chunk_id TEXT, change_type TEXT, summary TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by TEXT, CONSTRAINT chk_session_artifact_role CHECK (artifact_role IN ( 'analysis','doc_change','note','decision','risk','test_model' )), CONSTRAINT chk_session_change_type CHECK (change_type IS NULL OR change_type IN ( 'added','updated','removed','linked' )) ); CREATE INDEX IF NOT EXISTS idx_session_artifacts_session ON session_artifacts(session_id); CREATE INDEX IF NOT EXISTS idx_session_artifacts_project ON session_artifacts(project_id); CREATE INDEX IF NOT EXISTS idx_session_artifacts_role ON session_artifacts(artifact_role); -- 1) Story records CREATE TABLE IF NOT EXISTS story_records ( story_id TEXT PRIMARY KEY, project_id TEXT NOT NULL, title TEXT, status TEXT NOT NULL DEFAULT 'draft', baseline_commit_sha TEXT, snapshot_id TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by TEXT, updated_by TEXT, CONSTRAINT chk_story_status CHECK (status IN ( 'draft','in_progress','review','done','archived' )) ); CREATE INDEX IF NOT EXISTS idx_story_records_project ON story_records(project_id); CREATE INDEX IF NOT EXISTS idx_story_records_status ON story_records(status); CREATE INDEX IF NOT EXISTS idx_story_records_updated_at ON story_records(updated_at DESC); -- 2) Story artifacts CREATE TABLE IF NOT EXISTS story_artifacts ( id BIGSERIAL PRIMARY KEY, story_id TEXT NOT NULL REFERENCES story_records(story_id) ON DELETE CASCADE, artifact_role TEXT NOT NULL, doc_id TEXT, doc_version TEXT, path TEXT, section TEXT, chunk_id TEXT, change_type TEXT, summary TEXT, source_ref TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by TEXT, CONSTRAINT chk_story_artifact_role CHECK (artifact_role IN ( 'requirement','analysis','doc_change','test_model','note','decision','risk' )), CONSTRAINT chk_story_change_type CHECK (change_type IS NULL OR change_type IN ( 'added','updated','removed','linked' )) ); CREATE INDEX IF NOT EXISTS idx_story_artifacts_story_role ON story_artifacts(story_id, artifact_role); CREATE INDEX IF NOT EXISTS idx_story_artifacts_story_change ON story_artifacts(story_id, change_type); CREATE INDEX IF NOT EXISTS idx_story_artifacts_doc ON story_artifacts(doc_id, doc_version); CREATE INDEX IF NOT EXISTS idx_story_artifacts_path ON story_artifacts(path); -- Вариант уникальности можно уточнить после согласования процессов -- 3) Story links CREATE TABLE IF NOT EXISTS story_links ( id BIGSERIAL PRIMARY KEY, story_id TEXT NOT NULL REFERENCES story_records(story_id) ON DELETE CASCADE, link_type TEXT NOT NULL, target_ref TEXT NOT NULL, description TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT chk_story_link_type CHECK (link_type IN ( 'story','adr','ticket','pr','commit','doc','external' )) ); CREATE INDEX IF NOT EXISTS idx_story_links_story_type ON story_links(story_id, link_type); CREATE INDEX IF NOT EXISTS idx_story_links_target_ref ON story_links(target_ref); ``` ## 6) План внедрения (после согласования) 1. Подтвердить перечень полей и enum-значений. 2. Подготовить SQL-миграцию `Vxxx__story_context.sql`. 3. Обновить bootstrap/инициализацию схемы. 4. Обновить репозитории для `story_records/story_artifacts/story_links`. 5. Добавить таблицу и репозиторий `session_artifacts` (session-scoped артефакты без `story_id`). 6. Добавить запись session-артефактов в оркестраторе во время работы аналитика. 7. Добавить webhook-обработчик `bind session -> story` при появлении commit со `story_id`. 8. Добавить API/сервисный метод `get_story_context(story_id)` для повторного входа в Story. 9. Добавить тесты: - unit на репозитории, - интеграционные на happy-path записи/чтения, - регресс на отсутствие зависимости от типа RAG. 10. Добавить миграцию для `rag_blob_cache/rag_chunk_cache/rag_session_chunk_map`. 11. Внедрить `delta-only` индексацию для session-RAG с переиспользованием кэша. ## 7) Открытые вопросы - Нужен ли отдельный справочник для `artifact_type`, `artifact_role`, `link_type`. - Что считать `doc_version`: semver, дата, commit, hash файла. - Нужна ли soft-delete политика для Story. - Требуется ли аудит (кто/когда менял `summary` и связи). - Какой уровень обязательности `chunk_id` (опционален по умолчанию). - Нужна ли TTL/очистка для `rag_blob_cache/rag_chunk_cache`. - Делать ли fallback к репозиторному кэшу по умолчанию или только при explicit-флаге. - Как определять соответствие `session_id` и commit в webhook (1:1, последний активный, explicit token). - Как долго хранить `session_artifacts` до bind/cleanup. ## 8) Критерии готовности - По `story_id` можно восстановить инкремент без исходной сессии. - История изменений не теряется при переиндексации RAG. - Аналитик и тестировщик используют один `story_id` как общий ключ контекста. - Схема работает при любом выбранном режиме RAG. - Session-RAG поднимается быстрее за счет cache hit по неизмененным файлам. - Артефакты аналитика, созданные до появления `story_id`, корректно попадают в Story после commit/webhook bind.