272 lines
14 KiB
Markdown
272 lines
14 KiB
Markdown
# План доработки БД для хранения контекста 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.
|