feat(kebab-core + kebab-store-sqlite): p9-fb-17 chat session storage (V005) #80

Merged
altair823 merged 2 commits from feat/p9-fb-17-chat-storage into main 2026-05-03 05:41:01 +00:00
Owner

요약

multi-turn 영속화 (storage 만 — UI p9-fb-18 후속). chat_sessions header + chat_turns rows, ON DELETE CASCADE.

변경

  • V005 migration: chat_sessions + chat_turns + idx_chat_turns_session (모두 STRICT)
  • kebab_core::ChatSessionRepo trait (6 method) + ChatSessionRow / ChatTurnRow structs
  • SqliteStore impl in 신규 chat_sessions.rs 모듈
  • frozen design §5.7a 신설

HOTFIXES (V004 → V005)

spec 의 V004__chat_sessions.sql 가 p9-fb-19 의 V004__kv.sql (이미 머지) 와 refinery number 충돌 → V005__chat_sessions.sql 로 시프트. 동작 동일, 파일명만. HOTFIXES entry.

테스트

  • 9 신규 integration unit (roundtrip, missing, PK collision, append+list, dup index, updated_at bump, CASCADE, ORDER BY, LIMIT)
  • cargo test --workspace --no-fail-fast -j 1 exit 0
  • cargo clippy --workspace --all-targets -- -D warnings clean

문서

  • frozen design §5.7a
  • HANDOFF entry
  • HOTFIXES rename note
  • spec status planned → in_progress

unblocks p9-fb-18 (CLI session/repl).

## 요약 multi-turn 영속화 (storage 만 — UI p9-fb-18 후속). chat_sessions header + chat_turns rows, ON DELETE CASCADE. ## 변경 - **V005 migration**: chat_sessions + chat_turns + idx_chat_turns_session (모두 STRICT) - **`kebab_core::ChatSessionRepo`** trait (6 method) + `ChatSessionRow` / `ChatTurnRow` structs - **`SqliteStore` impl** in 신규 `chat_sessions.rs` 모듈 - **frozen design §5.7a** 신설 ## HOTFIXES (V004 → V005) spec 의 `V004__chat_sessions.sql` 가 p9-fb-19 의 `V004__kv.sql` (이미 머지) 와 refinery number 충돌 → `V005__chat_sessions.sql` 로 시프트. 동작 동일, 파일명만. HOTFIXES entry. ## 테스트 - 9 신규 integration unit (roundtrip, missing, PK collision, append+list, dup index, updated_at bump, CASCADE, ORDER BY, LIMIT) - `cargo test --workspace --no-fail-fast -j 1` exit 0 - `cargo clippy --workspace --all-targets -- -D warnings` clean ## 문서 - frozen design §5.7a - HANDOFF entry - HOTFIXES rename note - spec status planned → in_progress unblocks p9-fb-18 (CLI session/repl).
altair823 added 1 commit 2026-05-03 05:38:22 +00:00
도그푸딩 item 13/14 (multi-turn 영속화) — TUI Ask 의 "이전 대화
이어가기" + 향후 CLI `--session foo` (p9-fb-18) backing store. session
header + per-turn 두 테이블, ON DELETE CASCADE 로 reset --data-only 가
한꺼번에 wipe.

## 핵심 변경

- **SQLite V005 migration** `chat_sessions` (session_id PK + created_at
  + updated_at + title + config_snapshot_json) + `chat_turns` (turn_id
  PK + session_id FK ON DELETE CASCADE + turn_index + question +
  answer + citations_json + created_at + UNIQUE(session_id, turn_index))
  + `idx_chat_turns_session(session_id, turn_index)`. 모두 `STRICT`.
- **`kebab_core::ChatSessionRepo`** trait (6 method): create_session /
  get_session / list_sessions(limit, ORDER BY updated_at DESC) /
  delete_session / append_turn / list_turns(ORDER BY turn_index ASC)
- **`kebab_core::{ChatSessionRow, ChatTurnRow}`** structs — Serialize
  + Deserialize 둘 다 (CLI / wire 출력 호환)
- **`kebab-store-sqlite::SqliteStore`** impl 신규 모듈 `chat_sessions.rs`.
  `append_turn` 이 insert + parent updated_at bump 같은 connection
  에서 처리.
- **frozen design §5** 에 §5.7a chat_sessions / chat_turns 절 신설
  (full schema + trait 메서드 6 개 명시).

## HOTFIXES (V004 → V005)

spec p9-fb-17 의 `V004__chat_sessions.sql` 가 p9-fb-19 의
`V004__kv.sql` (이미 머지) 와 refinery migration number 충돌. 무중단
정정: `V005__chat_sessions.sql` 로 시프트. schema / 동작 동일, 파일명
만 이동. HOTFIXES entry 추가.

## 테스트

- 9 신규 integration unit (create/get roundtrip, missing→None, PK
  collision error, append+list ordered, dup turn_index error,
  append bumps updated_at, delete CASCADE turns, list_sessions
  ORDER BY updated_at DESC, list_sessions LIMIT)
- workspace 전체 `cargo test --workspace --no-fail-fast -j 1` exit 0
- `cargo clippy --workspace --all-targets -- -D warnings` clean

## 문서

- frozen design §5.7a 신설
- HANDOFF: 2026-05-03 entry
- HOTFIXES: V004 → V005 rename rationale
- spec status planned → in_progress

## Out of scope

- session 검색 / 필터 UI (p9-fb-18 의 `kebab ask --session list`
  같은 admin command 가 후속)
- 다른 store backend (postgres 등) — trait 만 정의, impl 은 SQLite

unblocks p9-fb-18 (CLI session/repl).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 requested changes 2026-05-03 05:39:36 +00:00
Dismissed
claude-reviewer-01 left a comment
Member

회차 1 — schema + trait 디자인 정석. ON DELETE CASCADE, UNIQUE(session_id, turn_index), STRICT 모드 등 안전장치 빠짐없음. 9 신규 integration unit 도 roundtrip / missing / collision / cascade / ORDER BY / LIMIT 골고루 커버.

actionable bug 1 건 — append_turn 의 doc 은 transaction 보장한다고 하지만 실제로는 auto-commit 두 번이라 두번째 실패 시 inconsistent. 단일 nit 라 회차 2 가능.

회차 1 — schema + trait 디자인 정석. ON DELETE CASCADE, UNIQUE(session_id, turn_index), STRICT 모드 등 안전장치 빠짐없음. 9 신규 integration unit 도 roundtrip / missing / collision / cascade / ORDER BY / LIMIT 골고루 커버. actionable bug 1 건 — append_turn 의 doc 은 transaction 보장한다고 하지만 실제로는 auto-commit 두 번이라 두번째 실패 시 inconsistent. 단일 nit 라 회차 2 가능.
@@ -0,0 +121,4 @@
],
)
.map_err(StoreError::from)
.context("append_turn: insert")?;

Real bug: doc comment 가 "Wrap insert + parent updated_at in one transaction" 라고 명시하지만, 실제 코드는 conn.execute 두 번을 그냥 호출합니다 — rusqlite 의 default 는 auto-commit 이라서 두 statement 가 별도 commit. 두 번째 (parent updated_at bump) 가 실패하면 첫 statement (turn insert) 는 이미 commit 됐고, 결과적으로 turn 은 있는데 session 의 updated_at 은 stale.

실용 시나리오: SQLite 가 두 번째 statement 처리 중 disk full / lock contention 으로 fail 하면 inconsistent state. spec 의 contract ("Bumps the parent's updated_at") 가 깨짐.

Fix:

let mut conn = self.lock_conn();
let tx = conn.transaction().map_err(StoreError::from).context("append_turn: begin")?;
tx.execute("INSERT INTO chat_turns ...", params![...]).map_err(...)?;
tx.execute("UPDATE chat_sessions SET updated_at = ? WHERE session_id = ?", params![turn.created_at, turn.session_id]).map_err(...)?;
tx.commit().map_err(StoreError::from).context("append_turn: commit")?;
Ok(())

주의: conn.transaction()&mut Connection 을 받아서 MutexGuard<Connection> 의 deref 가 Connection 인지 확인 필요. lock_conn()MutexGuard<Connection> 반환하면 let mut conn = ...; let tx = conn.transaction() 패턴 가능 (MutexGuard 가 DerefMut).

**Real bug**: doc comment 가 "Wrap insert + parent updated_at in one transaction" 라고 명시하지만, 실제 코드는 `conn.execute` 두 번을 그냥 호출합니다 — rusqlite 의 default 는 auto-commit 이라서 두 statement 가 별도 commit. 두 번째 (parent updated_at bump) 가 실패하면 첫 statement (turn insert) 는 이미 commit 됐고, 결과적으로 turn 은 있는데 session 의 updated_at 은 stale. 실용 시나리오: SQLite 가 두 번째 statement 처리 중 disk full / lock contention 으로 fail 하면 inconsistent state. spec 의 contract ("Bumps the parent's updated_at") 가 깨짐. Fix: ```rust let mut conn = self.lock_conn(); let tx = conn.transaction().map_err(StoreError::from).context("append_turn: begin")?; tx.execute("INSERT INTO chat_turns ...", params![...]).map_err(...)?; tx.execute("UPDATE chat_sessions SET updated_at = ? WHERE session_id = ?", params![turn.created_at, turn.session_id]).map_err(...)?; tx.commit().map_err(StoreError::from).context("append_turn: commit")?; Ok(()) ``` 주의: `conn.transaction()` 은 `&mut Connection` 을 받아서 `MutexGuard<Connection>` 의 deref 가 `Connection` 인지 확인 필요. `lock_conn()` 이 `MutexGuard<Connection>` 반환하면 `let mut conn = ...; let tx = conn.transaction()` 패턴 가능 (MutexGuard 가 DerefMut).
altair823 added 1 commit 2026-05-03 05:40:44 +00:00
- `append_turn` 의 doc 은 "wrap in one transaction" 보장하지만 실제
  코드는 auto-commit `conn.execute` 두 번이라 두번째 실패 시 first
  row 가 commit 된 채 inconsistent 됨. 진짜 transaction 으로 교체:
  `conn.transaction()` → `tx.execute(insert)` → `tx.execute(update)`
  → `tx.commit()`. SQLite BEGIN 으로 감싸 두 statement atomic.

`lock_conn()` 이 `MutexGuard<Connection>` 반환하므로 `let mut conn =
self.lock_conn(); let tx = conn.transaction()` 패턴 가능 (MutexGuard
의 DerefMut 활용).

9 chat_sessions 테스트 + clippy 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 approved these changes 2026-05-03 05:40:56 +00:00
claude-reviewer-01 left a comment
Member

회차 2 — append_turn 진짜 transaction 으로 교체됨 (begin → 2 execute → commit). 두 statement 가 atomic, 두번째 실패 시 첫번째도 rollback. 9 테스트 + clippy 통과. 머지 OK.

회차 2 — append_turn 진짜 transaction 으로 교체됨 (begin → 2 execute → commit). 두 statement 가 atomic, 두번째 실패 시 첫번째도 rollback. 9 테스트 + clippy 통과. 머지 OK.
altair823 merged commit 570fd86c2a into main 2026-05-03 05:41:01 +00:00
altair823 deleted branch feat/p9-fb-17-chat-storage 2026-05-03 05:41:02 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#80