chore: PR #235 회차 3 리뷰 반영 — 판정 보류 분기 + vector_store env
3회차 리뷰가 2회차 지적 8건이 동작 수준에서 해결됐음을 확인하고 머지 가능으로 결론냈다. 남은 LOW 중 값이 있는 셋을 반영한다. 1) 마이그레이션 이력을 못 읽었을 때 파괴적 조언으로 떨어졌다 `Some(Some((None, Ok(어긋남))))` — 버전은 모르는데 표본은 읽힌 경우 — 가 `ok: false` + "`kebab reset`" 분기로 갔다. 버전을 모르는 것은 KB 를 날리라고 말할 근거가 못 된다. 판정을 "V016 이상임이 확인된 스토어"로 한정하고, 이력을 못 읽으면 `ok: true` + "판정 보류" 로 둔다. 실제 도달 경로는 사실상 없지만(chunks_fts 가 있으면 이력도 있다) 분기의 기본값이 파괴적인 쪽인 게 문제였다. 2) "점검하지 못했다" hint 가 가장 흔한 원인을 안 적었다 2회차에서 고친 HIGH — 이전 버전 doctor 가 남긴 0바이트 kebab.sqlite — 가 정확히 이 분기로 온다. 테이블이 없어 읽기에 실패한다. "아직 색인 전이거나" 를 앞에 넣었다. 3) vector_store 블록이 아직 env 를 안 얹고 있었다 2회차에서 fts_shadow 에 대해 고친 것과 같은 결함이 #234 에서 들어온 바로 아래 블록에 그대로 남아 있었다. 정보성 체크라 종료 코드에는 영향이 없지만 detail 이 엉뚱한 디렉토리를 가리킨다. 같이 맞췄다. 미반영: shadow 의 chunk_id 가 NULL 이면 분자에서 빠지는 과소 계수 — 트리거가 NOT NULL 컬럼을 미러링하므로 도달 불가이고 방향도 오탐이 아닌 과소 쪽이다. 새 테스트가 `KEBAB_STORAGE_DATA_DIR` 이 export 된 셸에서 실패하는 것은 점검이 제대로 동작하는 결과라, 원인을 빨리 찾도록 테스트 주석에 적어 두는 선에서 끝냈다. 실측 확인: KB 없음 / V015 / V016 / env override 네 경로 모두 의도한 문구가 나오고, KB 없는 경로는 파일을 남기지 않는다. env override 를 주면 fts_shadow 와 vector_store 가 같은 디렉토리를 본다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017c9JwQq8ZkGvYjpKXMiDhF
This commit is contained in:
@@ -499,36 +499,47 @@ pub fn doctor_with_config_path(
|
||||
true,
|
||||
"점검하지 못했다".to_string(),
|
||||
Some(
|
||||
"SQLite 를 열거나 읽지 못했다 — 다른 kebab 프로세스가 쓰는 중이거나 \
|
||||
DB 가 손상됐을 수 있다"
|
||||
"SQLite 를 열거나 읽지 못했다 — 아직 색인 전이거나, 다른 kebab \
|
||||
프로세스가 쓰는 중이거나, DB 가 손상됐을 수 있다"
|
||||
.to_string(),
|
||||
),
|
||||
),
|
||||
// Pre-V016 stores can be misaligned already: V009's backfill
|
||||
// inserted without an explicit rowid, so a store with gaps in
|
||||
// `chunks.rowid` at that moment got a densely-numbered shadow.
|
||||
// That is harmless there — deletes still address rows by
|
||||
// chunk_id. Applying V016 repopulates with explicit rowids and
|
||||
// fixes it, so the advice is "migrate", never "wipe".
|
||||
Some(Some((Some(v), _))) if v < V016 => (
|
||||
true,
|
||||
format!("V016 적용 전 (현재 V{v:03}) — 마이그레이션 후 점검된다"),
|
||||
None,
|
||||
),
|
||||
Some(Some((_, Ok((checked, 0))))) => (
|
||||
true,
|
||||
format!("chunks_fts rowid 정렬 정상 (양끝 {checked}행 표본)"),
|
||||
None,
|
||||
),
|
||||
Some(Some((_, Ok((checked, bad))))) => (
|
||||
false,
|
||||
format!("chunks_fts rowid 정렬 어긋남 — 표본 {checked}행 중 {bad}행 불일치"),
|
||||
Some(
|
||||
"삭제가 엉뚱한 FTS 행을 지우고 있다. `kebab reset` 후 재색인으로 \
|
||||
인덱스를 다시 만들어라"
|
||||
.to_string(),
|
||||
// A verdict is only rendered for a store known to be V016 or
|
||||
// later. Pre-V016 stores can be misaligned already — V009's
|
||||
// backfill inserted without an explicit rowid, so a store
|
||||
// with gaps in `chunks.rowid` at that moment got a densely
|
||||
// numbered shadow — and there it is harmless, because
|
||||
// deletes still address rows by chunk_id. Applying V016
|
||||
// repopulates with explicit rowids and fixes it, so the
|
||||
// advice is "migrate", never "wipe". An unreadable history
|
||||
// takes the same branch: not knowing the version is not
|
||||
// grounds for telling someone to destroy their KB.
|
||||
Some(Some((ver, Ok((checked, bad))))) => match ver {
|
||||
Some(v) if v >= V016 && bad > 0 => (
|
||||
false,
|
||||
format!("chunks_fts rowid 정렬 어긋남 — 표본 {checked}행 중 {bad}행 불일치"),
|
||||
Some(
|
||||
"삭제가 엉뚱한 FTS 행을 지우고 있다. `kebab reset` 후 재색인으로 \
|
||||
인덱스를 다시 만들어라"
|
||||
.to_string(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Some(v) if v >= V016 => (
|
||||
true,
|
||||
format!("chunks_fts rowid 정렬 정상 (양끝 {checked}행 표본)"),
|
||||
None,
|
||||
),
|
||||
Some(v) => (
|
||||
true,
|
||||
format!("V016 적용 전 (현재 V{v:03}) — 마이그레이션 후 점검된다"),
|
||||
None,
|
||||
),
|
||||
None => (
|
||||
true,
|
||||
"판정 보류 — 마이그레이션 이력을 읽지 못했다".to_string(),
|
||||
Some("kebab 이 만든 DB 가 아닐 수 있다".to_string()),
|
||||
),
|
||||
},
|
||||
};
|
||||
checks.push(DoctorCheck {
|
||||
name: "fts_shadow".to_string(),
|
||||
@@ -540,9 +551,16 @@ pub fn doctor_with_config_path(
|
||||
|
||||
// vector_store — Lance fragment / version-history health (issue #230).
|
||||
{
|
||||
let cfg = loaded_cfg
|
||||
.clone()
|
||||
.unwrap_or_else(kebab_config::Config::defaults);
|
||||
// Same env precedence as the two checks above — this block
|
||||
// landed in #234 reading the file-only config, so a
|
||||
// KEBAB_STORAGE_DATA_DIR user got stats for the wrong directory.
|
||||
let cfg = match loaded_cfg.as_ref() {
|
||||
Some(c) => {
|
||||
let env: std::collections::HashMap<String, String> = std::env::vars().collect();
|
||||
c.clone().apply_env(&env)
|
||||
}
|
||||
None => kebab_config::Config::defaults(),
|
||||
};
|
||||
let data_dir = kebab_config::expand_path(&cfg.storage.data_dir, "");
|
||||
let vector_dir =
|
||||
kebab_config::expand_path(&cfg.storage.vector_dir, &data_dir.to_string_lossy());
|
||||
|
||||
@@ -123,6 +123,11 @@ fn doctor_does_not_create_a_store_where_none_exists() {
|
||||
/// trigger still addresses rows by chunk_id, so the drift is harmless.
|
||||
/// Telling that user to `kebab reset` would destroy a healthy KB over a
|
||||
/// condition that applying the migration fixes by itself.
|
||||
///
|
||||
/// Note: doctor applies env overrides with the same precedence as
|
||||
/// `Config::load`, so `KEBAB_STORAGE_DATA_DIR` exported in the shell
|
||||
/// redirects this check away from the temp store and fails the test.
|
||||
/// That is the check behaving correctly, not a broken test — unset it.
|
||||
#[test]
|
||||
fn doctor_does_not_call_a_pre_v016_store_broken() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user