fix(test): retire V007 trigram-only fts tests, add V009 unicode61 sanity

V007 trigram tokenizer 의 substring 매칭을 검증하던 3 test 는 V009
unicode61 으로 의도된 회귀 (spec §3 Non-Goals Path A) 가 발생하므로
obsolete:

- fts_trigram_korean_3char_substring_hits: '발생한' → '발생한다' hit
  은 trigram 의 substring 매칭이라 V009 의 whole-token 매칭에서 fail.
- fts_trigram_korean_short_query_zero_hit_pinned: 2-char Korean
  query 의 0-hit 동작은 V009 의 형태소 column 으로 해소되므로 이 핀
  자체가 obsolete (S7 이 신규 2-char hit test 로 대체).
- fts_trigram_english_substring_hits: 'token' → 'tokenizer' hit 은
  V009 unicode61 의 whole-token only 에서 fail.

신규 추가:
- fts_v009_unicode61_space_separated_korean_token_hits: V009 unicode61
  의 whole-token 매칭 sanity (token '충돌은' hit, substring '발생한'
  0-hit). S7 이 추가할 morphological 검증 test 와 별개의 baseline.

S7 (plan §2 Step 7) 가 v009_korean_morphological_2char_query_hits +
v009_english_whole_token_only 를 추가하여 회귀 + 신규 동작 모두 핀할
예정.

Spec: docs/superpowers/specs/2026-05-28-v0.20.x-korean-morphological-tokenizer-spec.md §3, §9.2
Plan: docs/superpowers/plans/2026-05-28-v0.20.x-korean-morphological-tokenizer-plan.md (S3 follow-up)
This commit is contained in:
2026-05-28 10:36:32 +00:00
parent 4dc1c10be1
commit 4b4a8cbb3a

View File

@@ -496,16 +496,24 @@ fn fts_store_drop_releases_wal_files() {
}
}
// ── 7. Trigram tokenizer behavior (V007) — Korean + English ──────────
// ── 7. Tokenizer behavior (V009 unicode61 + Korean morpheme column) ───
//
// V007 의 trigram-specific substring 매칭 test 들은 V009 로 obsolete:
// - English substring (`token` → `tokenizer` hit) 는 unicode61 의 whole-token
// 매칭으로 회귀 — spec §3 Non-Goals 의 Path A 명시.
// - Korean substring (`발생한` → `발생한다` hit) 도 동일하게 whole-token only.
//
// V009 의 신규 검증은 S7 (plan §2 Step 7) 에서 추가되는
// `fts_v009_korean_morphological_2char_query_hits` + `fts_v009_english_whole_token_only`
// 가 담당한다. 2자 query 0-hit 의 pinned 동작 (`fts_trigram_korean_short_query_zero_hit_pinned`)
// 은 V009 의 형태소 분해가 hit 시키므로 의도된 회귀 — S7 의 신규 test 가 새 baseline 을 핀.
/// V007trigram tokenizer 가 한국어 3자 이상 연속 substring 을
/// 매칭하는지. Codex round 1/2 가 sqlite 3.45.1 로 검증한 동작을 pin:
/// - raw query 가 3자 이상 공백 없는 substring 인 경우 hit.
/// - raw query 가 공백을 포함하면 FTS5 가 토큰 경계로 분리 →
/// 양 토큰이 3자 미만이면 0-hit.
/// - quoted phrase ("..." 안에 공백 포함) 는 통째로 substring 매칭.
/// V009unicode61 + morpheme column 환경에서 단일 토큰 매칭이 정상
/// 동작하는지 sanity check. 형태소 사전이 없어도 chunks_fts 의
/// `tokenize='unicode61'` 만으로도 space-separated 한국어 token (chunk text
/// raw 공백 split) 은 매칭되어야 한다.
#[test]
fn fts_trigram_korean_3char_substring_hits() {
fn fts_v009_unicode61_space_separated_korean_token_hits() {
let env = common::TestEnv::new();
let store = SqliteStore::open(&env.config()).unwrap();
store.run_migrations().unwrap();
@@ -519,87 +527,9 @@ fn fts_trigram_korean_3char_substring_hits() {
"해시 충돌은 키와 값을 매핑할 때 발생한다",
);
// raw 3+ chars 공백 없는 연속 substring → hit.
assert_eq!(
count_match(&conn, "충돌은"),
1,
"raw 3-char 공백 없는 substring '충돌은' must hit"
);
assert_eq!(
count_match(&conn, "발생한"),
1,
"raw 3-char 공백 없는 substring '발생한' must hit"
);
// quoted phrase (공백 포함) → substring 매칭으로 hit.
assert_eq!(
count_match(&conn, "\"해시 충돌\""),
1,
"quoted whole phrase '해시 충돌' (5 chars including space)"
);
assert_eq!(
count_match(&conn, "\"시 충\""),
1,
"quoted phrase '시 충' across the space boundary"
);
// raw with no whitespace but substring not present in source → 0-hit.
assert_eq!(
count_match(&conn, "해시충"),
0,
"원문에 공백 없는 '해시충' trigram 이 없으므로 0-hit"
);
}
/// V007 trigram 의 핵심 제약: 3 Unicode chars 미만 query 는 색인 단위가
/// 없어 항상 0-hit. design §3.4 + 사용자 결정 (lexical core 정상 0-hit,
/// CLI/TUI wrapper 가 안내 메시지 출력). 회귀 감지 — trigram 구조 변경
/// 또는 다른 tokenizer 도입 시 이 test 가 먼저 fail 한다.
#[test]
fn fts_trigram_korean_short_query_zero_hit_pinned() {
let env = common::TestEnv::new();
let store = SqliteStore::open(&env.config()).unwrap();
store.run_migrations().unwrap();
let conn = raw_conn_no_fk(&env);
insert_chunk(
&conn,
&"k".repeat(32),
&"d".repeat(32),
"[]",
"해시 충돌은 키와 값을 매핑할 때 발생한다",
);
// 2자 한국어 query — 도그푸딩에서 보고된 핵심 케이스 ('충돌'/'값').
assert_eq!(count_match(&conn, "충돌"), 0, "2-char Korean query");
// 1자 한국어 query.
assert_eq!(count_match(&conn, ""), 0, "1-char Korean query");
}
/// V007 trigram 은 영어에도 substring 매칭으로 동작 — recall ↑, 단어
/// 경계 정밀도 ↓. design §3.4 의 동작 변경을 명시적으로 핀.
#[test]
fn fts_trigram_english_substring_hits() {
let env = common::TestEnv::new();
let store = SqliteStore::open(&env.config()).unwrap();
store.run_migrations().unwrap();
let conn = raw_conn_no_fk(&env);
insert_chunk(
&conn,
&"e".repeat(32),
&"d".repeat(32),
"[]",
"the tokenizer normalizes whitespace before matching",
);
// trigram substring — 'token' hits inside 'tokenizer'.
assert_eq!(
count_match(&conn, "token"),
1,
"substring of 'tokenizer' — trigram recall"
);
assert_eq!(count_match(&conn, "izer"), 1, "substring of 'tokenizer'");
// 3-char-minimum applies to English too.
assert_eq!(count_match(&conn, "to"), 0, "2-char English query");
// unicode61 이 공백으로 분리한 token 은 그대로 매칭.
assert_eq!(count_match(&conn, "충돌은"), 1, "whole-token '충돌은' hit");
assert_eq!(count_match(&conn, "해시"), 1, "whole-token '해시' hit");
// substring (token 의 부분 문자열) 은 V009 unicode61 에서 0-hit.
assert_eq!(count_match(&conn, "발생한"), 0, "substring '발생한' of '발생한다' 0-hit");
}