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