Files
kebab/docs/superpowers/specs/2026-05-28-v0.20.x-korean-morphological-tokenizer-spec-critic-r1.md
altair823 8c56ef3010 docs(superpowers): v0.20.x C 한국어 morphological tokenizer spec + plan artifacts
본 commit 은 v0.20.x C task (Bug #8 — 한국어 2자 query 0-hit) 의
4-stage workflow artifact 5 파일을 archive:

- spec.md (668 line, status=accepted): Option A/B/C 비교 + lindera
  Path A (영어 substring 회귀 인정) 결정 + 12 section + 4 Appendix
  (B segmentation evidence, C cost evidence, D license evidence).
- spec-critic-r1.md: 3 critical + 6 major finding (NEEDS_REWRITE).
- spec-critic-r2.md: r1c rewrite 후 traceability matrix (ACCEPT).
- plan.md (750 line, status=accepted): 11 step + dependencies +
  cost optimization routing + 9 closure micro-patches 적용.
- plan-closure-r1.md: traceability matrix + 9 MP 의 origin.

이 artifact 들은 implementation 머지 후 frozen reference. 후속
deviation 은 tasks/HOTFIXES.md 가 source of truth.

Workflow stage:
1. spec drafter (omc team writer, opus)
2. spec critic R1 (omc team critic, opus) — NEEDS_REWRITE
3. spec rewriter r1c (omc team writer, opus) — 7 item fix
4. spec closure R2 (in-process verifier, sonnet) — ACCEPT
5. plan drafter (omc team planner, opus)
6. plan closure (in-process verifier, sonnet) — ACCEPT + 9 MP
7. subagent-driven-development implementation (11 step + 5 follow-up
   + 1 docs polish = 17 commit)
8. PR-level final code review (in-process code-reviewer, opus) —
   Approved with notes (4 minor docs finding, merge as-is)

Branch: feat/korean-morphological-tokenizer
Version: 0.20.1
2026-05-28 12:53:31 +00:00

27 KiB
Raw Permalink Blame History

Spec critic round 1 — 한국어 morphological tokenizer

Verdict: NEEDS_REWRITE Reviewed by: critic R1 Reviewed at: 2026-05-28 Target spec: docs/superpowers/specs/2026-05-28-v0.20.x-korean-morphological-tokenizer-spec.md

본 critic round 1 의 결론을 먼저 적자면 NEEDS_REWRITE 다. 본 spec 은 방향 (lindera + 별 column pre-tokenize) 자체는 합리적이나, 다음 세 가지 critical 결함이 동시에 존재해 그대로 implementation 으로 넘기면 silent regression / contractual 모순 / silent stale 데이터 손해 가 동시에 발생할 위험이 있다:

  1. tokenize='trigram''unicode61' 전환을 "English 변경 없음" 으로 선언한 §3 + §9.2 와, 실제 SQLite FTS5 의 substring 매칭 동작 사이의 직접 모순.
  2. §9.1 AC + §12.1 release notes 의 "기존 KB 자동 backfill (re-ingest 불필요)" claim 과, §8.2 의 lazy backfill (tokenized_korean_text = NULL 유지) 설계 사이의 직접 모순.
  3. unicode61 의 CJK tokenization 동작 (한 syllable run = 단일 token) 에 대한 spec 의 전제와, lindera ko-dic 의 segmentation 결과가 일치하지 않을 가능성 — §9.1 AC 의 2-char '한국' query 가 '한국어' chunk 에서 hit 한다는 보장이 깨질 수 있음.

이 셋은 모두 design / behavior surface 변경을 요구해 stylistic 수준 fix 로 해소 불가. 아래 finding 별 상세.


Substantive findings

Finding #1: English substring 회귀 — spec 의 self-contradiction (CRITICAL)

  • Severity: critical
  • Location: §3 Non-Goals (line 50), §4.1 표 의 "English 영향: 변경 없음" (line 66), §6.3 (line 192), §9.2 test fts_v009_english_substring_retained (line 271-275)
  • Issue:

V007 (tokenize='trigram') 의 핵심 trade-off 는 "영어 lexical 도 substring 매칭으로 이동" 이라고 design §5.5 line 1069-1070 + V007 migration line 13-15

  • HANDOFF.md line 42 가 모두 명시. 즉 V007 의 'token' query 가 'tokenizer' chunk 를 hit 시키는 동작은 trigram 의 결과로 도입된 신 behavior.

V009 spec 은 tokenize='unicode61' 로 복귀하면서 §3 / §4.1 / §9 모두에서 "English 변경 없음" 을 claim 하지만 이는 FTS5 의 동작과 정면 모순:

  • unicode61 은 whole-token (word) 매칭. 'token' query 는 'token' token 만 매칭하지 'tokenizer' 는 매칭 X.
  • tokenized_korean_text column 은 한국어 morpheme 분해 결과만 채우므로 English token 의 substring 매칭은 어떤 column 에서도 복원되지 않음.

§9.2 의 test 는 이 모순을 그대로 노출:

#[test]
fn fts_v009_english_substring_retained() {
    // Fixture: "the tokenizer normalizes whitespace" chunk.
    // Query: "token" → hit (substring of "tokenizer").
}

이 test 는 unicode61 위에서 logically impossible — 절대 통과 불가. test 가 통과한다면 그건 unicode61 이 아니라 다른 tokenizer 가 실제 적용 중이라는 뜻이고, test 가 fail 하면 §3 의 "변경 없음" claim 이 위약.

  • Suggested fix: 두 가지 중 하나로 spec 갱신 필요:
    • Option A (회귀 인정): §3 Non-Goals 에서 "V007 trigram 의 substring 매칭 유지" 제거. 새 표현: "English lexical 은 unicode61 의 whole-token 매칭으로 환원 — V002 (pre-v0.17.0) 와 동일." §4.1 의 "English 영향: 변경 없음" → "회귀 (substring → whole-token), V002 동일." Release notes (§12.1) 에 명시. test 이름 fts_v009_english_whole_token_only 로 rename
      • assertion 반전 ('token' → 0-hit on 'tokenizer' chunk).
    • Option B (dual-tokenizer 도입): English 의 substring 매칭을 유지하려면 chunks_fts 의 text column 은 trigram 유지, 별 column (예: kor_text) 에 unicode61 + morpheme-pre-tokenize. 단일 chunks_fts 의 column 별 tokenizer 가 FTS5 에서 지원 안 되므로 (한 tokenizer per virtual table), 실질적으로 dual virtual table = Option B (Bigram supplement) 와 같은 아키텍처 복잡도 → spec 의 §4.2 권장 근거 자체가 흔들림.

선택 자체는 architect 의 결정 사항이지만, 현재 spec 의 self-contradiction 은 NEEDS_REWRITE trigger.


Finding #2: 기존 KB 의 자동 backfill claim 위약 (CRITICAL)

  • Severity: critical
  • Location: §5.1 backfill INSERT (line 148-154), §8.2 (line 230-233), §9.1 (line 244) "현재 0 hit → 예상 hit", §9.3 verifier checklist (line 300), §12.1 release notes (line 365)
  • Issue:

§12.1 release notes 의 마지막 항목 "기존 KB 의 자동 backfill (재-ingest 불필요)" 와 §8.2 의 다음 두 문장 사이에 직접 모순:

  1. tokenized_korean_text 는 초기에 NULL (기존 chunks 에 형태소 분해 불가).
  2. Lazy backfill: 향후 ingest 시 기존 chunks 를 --force-reingest 로 재처리하면 tokenized_korean_text 채워짐 (선택 사항, user 가 원하면).

§5.1 의 backfill INSERT block 도 마찬가지로 CASE WHEN tokenized_korean_text IS NOT NULL THEN ... ELSE text END — 기존 chunks 는 항상 ELSE branch 를 타서 raw text 만 chunks_fts 에 들어감.

Raw text'한국문화는오래되었다' 같은 한국어 chunk 일 때, unicode61 은 CJK character run 을 단일 token 으로 봐서 token = '한국문화는오래되었다'. 2-char '한국' query 의 unicode61 tokenized form = '한국' token → FTS5 는 token 일치만 매칭하므로 0-hit.

즉 V009 migration 이 적용된 기존 KB 의 한국어 2-char query 는 여전히 0-hit. 사용자가 kebab ingest --force-reingest 를 명시적으로 호출하기 전까지 §9.1 AC 의 "현재 0 hit → 예상 hit" 가 충족 안 됨. v0.17.0 trigram adoption (V007) 의 "자동 backfill 로 즉시 효과" 사용자 기대 와 정반대.

  • Suggested fix: 두 가지 중 하나로 spec 갱신 필요:

    • Option A (eager backfill 채택): V009 migration 본체 내부 또는 첫 kebab invocation 의 booting hook 에서 모든 기존 chunks 에 대해 lindera tokenize → tokenized_korean_text UPDATE → chunks_fts re-index. Migration 시점이라 Rust helper 호출이 어려우면 (refinery 는 raw SQL 만 실행), V009 는 schema 만 변경 + kebab-app 의 first-boot hook 또는 별 kebab reindex-korean subcommand 로 backfill. §10 위험 표에 "backfill 시간 (KB 크기 비례, 1만 chunk 당 ~30-60s) 동안 search 가 부분 결과 반환" 추가.
    • Option B (lazy backfill 명시): §12.1 release notes 에서 "자동 backfill" 표현 삭제 → "기존 KB 는 kebab ingest --force-reingest 후에 2-char Korean query 활성화" 로 정직하게 표기. §9.1 AC 도 "fresh KB (V009 이후 ingest) 시나리오에 한정" 으로 scope 좁힘. dogfood instructions 명시.
  • Cross-link: §9.3 verifier checklist [ ] Ingest 후 chunks.tokenized_korean_text 가 모든 한국어 chunk 에 채워짐 도 "기존 chunk" / "신규 ingest" 분기 모호 — 갱신 필요.


Finding #3: unicode61 CJK tokenization 의 sub-morpheme 매칭 보장 부재 (CRITICAL)

  • Severity: critical
  • Location: §9.1 query scenario 1 (line 241-243), §6.2 (line 178-184), §3 Goals 첫 항목 (line 43)
  • Issue:

§9.1 의 첫 AC:

1. `kebab search '한국'` (2자)
   - 예상 hit: Korean wiki 의 "한국어", "한국 문화" 등 포함 chunk.

이 시나리오의 hit 보장은 다음 두 가지에 모두 의존:

(a) 별 tokenized_korean_text column 에 lindera ko-dic 의 segmentation 결과 (공백 구분) 가 저장 + FTS5 unicode61 이 공백을 token 경계로 인식. (b) lindera ko-dic 이 "한국어"["한국", "어"] 로 분해 (즉 sub-morpheme level 분해).

(a) 는 spec 설계상 성립. (b) 는 ko-dic 의 사전 정의에 달림. ko-dic 은 실제로는 "한국어"단일 명사로 등록 한 경우가 일반적이며, 그렇다면 lindera 의 출력은 ["한국어"] 한 token. 이때 unicode61 의 tokenization 이후 chunks_fts 의 token 은 '한국어' 이고, '한국' query token 과는 다른 token → 0-hit.

§6.2 의 '한국문화는오래되었다' 예시도 ['한국', '문화', '는', '오래', '되', '었다'] 식 segmentation 을 가정하나, 이게 실제 ko-dic 출력인지 spec drafter 가 검증한 evidence 가 없음. ko-dic 의 명사구 등록 정책상 '한국문화', '한국문화는' 등이 단일 entry 일 수 있고, 그러면 '한국' query 매칭은 실패.

본 critic 은 lindera ko-dic 의 실제 output 을 직접 실행해 검증할 수단이 없으므로 (PR 단계의 spike), spec 이 이 검증을 implementation 시점으로 미루는 것은 "구현 후 동작 안 하면 알게 됨" 형태의 design risk. AC §9.1 의 hit 보장 이 design level 에서 사라짐.

  • Suggested fix: spec drafter 가 spec 단계에서 다음 두 검증을 evidence 로 첨부:
    • 검증 1: 호스트 머신에서 lindera-cli + lindera-dict-ko-dic 으로 '한국어', '한국문화는오래되었다', '서울특별시', '지하철은 빠르다' 4-5 가지 fixture 에 대한 실제 tokenization 결과를 spec appendix 에 기록.
    • 검증 2: 만일 (예상대로) '한국어'['한국어'] 단일 token 으로 나온다면, AC §9.1 의 "한국어 포함 chunk hit" claim 을 삭제하거나, 또는 N-gram supplement (1자/2자 sub-token 도 추가 emit) 같은 추가 design 을 §6.2 에 추가. 이 추가는 §4.1 권장 (Option A 의 simplicity) 의 근거 자체를 약화시킴.

이 finding 은 §9.1 AC 의 의미 자체가 implementation-validatable 아닌 상태로 남는다는 점에서 NEEDS_REWRITE.


Finding #4: V007 CI diff-check (fts_v007_matches_design_section_5_5_verbatim) 의 운명 미명시 (MAJOR)

  • Severity: major
  • Location: §5.3 (line 162-166), 기존 test crates/kebab-store-sqlite/tests/fts.rs:407
  • Issue:

§5.3 은 신규 test fts_v009_matches_design_section_5_5_verbatim 추가만 명시. 기존 fts_v007_matches_design_section_5_5_verbatim 의 운명은 침묵.

기존 test 는 migrations/V007__fts_trigram.sql§5.5 verbatim block 을 design §5.5 의 verbatim 과 일치 비교. design §5.5 가 V009 의 unicode61 + 형태소 column 으로 다시 쓰여지면, V007 test 가 즉시 failmigrations/V007__fts_trigram.sqltokenize='trigram' 이 design 의 tokenize='unicode61' 와 안 맞으므로.

세 가지 가능한 처리 중 spec 이 어느 쪽을 선택해야 하는지 명시 필요:

  1. Rename + replace: fts_v007_matches_design_section_5_5_verbatimfts_v009_matches_design_section_5_5_verbatim 로 rename 하고 migration_block 추출 대상도 V009 로 변경. V007 은 "historical replay 만, 더 이상 design 와 매칭 X" 가 됨. → fts.rs:402-405 의 comment 와 호환 가능 (V002 가 이미 유사 처리).
  2. Two tests: V007 test 는 그대로 두되 design 비교를 끊고, 신규 V009 test 가 design 비교 담당. V007 test 는 syntactic correctness 만 확인 (migration 파일 존재 + DDL parse) 수준으로 축소.
  3. Delete V007 test: V002 는 fts.rs comment 에서 "더 이상 design 와 비교 안 함" 으로 표현됐는데, V007 도 동일 운명 명시.
  • Suggested fix: §5.3 에 위 3 가지 중 권장 옵션 명시 + tests/fts.rs 편집 범위 명시. PR scope 에 포함.

Finding #5: chunks 의 트리거 + ingest 파이프라인 순서의 race + double-index 가능성 (MAJOR)

  • Severity: major
  • Location: §5.1 chunks_ai trigger (line 121-128), §6.2 pre-tokenize 순서 (line 179-184)
  • Issue:

§6.2 의 ingest 흐름:

1. Chunk 생성 후 → 2. lindera tokenize → 3. Chunk row INSERT 시 tokenized_korean_text pre-fill

이 순서가 보장되면 chunks_ai trigger 가 fire 할 때 new.tokenized_korean_text 가 이미 채워져 있어 CASE 의 NOT NULL branch 로 정상 indexing.

하지만 §8.2 lazy backfill flow 는:

  • 기존 chunk: INSERT 시 tokenized = NULL → chunks_ai 가 ELSE branch (raw text 만 index) → 이후 user 가 --force-reingest 또는 background job 으로 UPDATE chunks SET tokenized_korean_text = '...' → chunks_au 가 DELETE
    • INSERT (CASE 의 NOT NULL branch).

이 두 path 가 같은 chunk 에 대해 동시 발생할 가능성 (예: 동일 chunk_id 가 서로 다른 ingest run 에서 reprocess) 의 race 명시 없음. 또한:

  • chunks_au 는 DELETE + INSERT 패턴이라 trigger 가 atomic 한 transaction 안에 실행되긴 함. 그러나 spec §6.2 단계 2 ("lindera tokenize") 와 단계 3 ("INSERT") 가 다른 transaction 이면, 단계 2 실패 시 chunks 는 row 없이 남거나 NULL tokenized 로 INSERT — recovery 정책 없음.

  • chunks_ai 의 INSERT 가 (chunk_id, doc_id, heading_path, text) 4-column. V007 migration 의 verbatim block 과 일치하므로 CI diff-check 가 통과하려면 signature 일치해야 함 — 하지만 §5.1 의 trigger body 가 4-column 으로는 맞아도 VALUES 부 는 CASE expression 으로 raw text 와 다름 → CI diff-check 의 "verbatim" 의미가 column 단위인지 statement 단위인지 명확화 필요.

  • Suggested fix:

    • §6.2 단계 명시: lindera tokenize 는 chunk row INSERT 와 동일 transaction 안에서 (Rust 측에서 string 계산 후 단일 INSERT). NULL backfill 경로 외에는 chunks_ai trigger 가 항상 CASE 의 NOT NULL branch 를 타는 invariant 보장.
    • CI diff-check 의 "verbatim" 정의 (whitespace-normalized string compare of the §5.5 block) 가 CASE expression 까지 포함하는지 명시. V007 의 fts.rs:407 test 는 string compare 라 CASE 추가는 verbatim 변경 → design §5.5 도 같이 변경 필요.
    • tokenize_korean_morphological() 의 실패 처리 (예: lindera dict load fail) — fallback (NULL) vs error propagation 정책. spec 침묵.

Finding #6: storage / binary 비용 추정의 evidence 부재 (MAJOR)

  • Severity: major
  • Location: §4.1 표 (line 62 "DB 크기 +20-30%"), §10.2 (line 316-317 "Dict 7-10 MB, binary +5-10 MB"), §10.3 (line 322 "Ingest +10-20%")
  • Issue:

세 estimate 모두 spec 내부에 측정 / 근거 / 참조 없음. 다음 의심점:

  1. DB 크기 +20-30%: chunks 에 추가되는 tokenized_korean_text column 본문 = 한국어 chunk 의 segmented form (대략 +1× 원문) + chunks_fts 가 index 하는 text 가 tokenized_korean_text || ' ' || text = 2× Korean text. Korean-heavy KB (한국어 wiki 위주) 면 chunks 테이블 본체 +50-80% + chunks_fts shadow ~+100% Korean part 만 — 합산 "+20-30%" 은 영문 위주 KB 에서나 성립할 보수적 lower bound. 한국어 위주 dogfood KB 에서는 훨씬 클 가능성.

  2. Dict size 7-10 MB compressed: lindera-dict-ko-dic crate 의 실제 uncompressed dict size 는 30-50 MB 수준 (FST + matrix + connection cost table). Cargo crate 의 packed size 가 ~20-30 MB. Release binary 에 embed (include_bytes!) 시 +20-30 MB 가 정상 추정. "+5-10 MB" 은 LTO 와 strip 의 산술 misapplied 가능성 — dict 자체는 strip 대상 아님.

  3. Ingest +10-20%: chunk creation 자체가 ingest 의 일부분이고, lindera tokenize 는 chunk text 길이 비례. 1000-char chunk 당 5-20 ms 라는 §10.3 추정도 lindera benchmark 출처 없음. 대형 PDF (수백 chunk) ingest 에서는 누적 latency 가 +30-50% 가능성 — 별 mitigation 필요.

  • Suggested fix: spec drafter 가 다음 measurement 을 spec appendix 에 첨부 (PR 단계에서 spike branch 로 측정 가능):
    • lindera-ko-dic 의 uncompressed size + cargo packed size + release binary 의 strip 후 size delta.
    • 한국어 wiki fixture KB (예: dogfood-p10b/) 에 V009 적용 전후 SQLite 파일 size delta.
    • 같은 fixture 의 kebab ingest total time 전후 비교 (warm + cold both).

evidence 없으면 §4.1 의 Option A 선택 근거 (storage 비용 표) 자체가 부실 → post-merge 에서 "예상보다 큼" 발견 시 design rollback 위험.


Finding #7: search result ordering 변화 / eval baseline drift 미주소 (MAJOR)

  • Severity: major
  • Location: §11 (line 332-352)
  • Issue:

V007 trigram → V009 unicode61 + 형태소 전환은 token boundary 자체가 다름 (3-gram vs whole-word vs morpheme). 같은 query 의 BM25 raw score 분포가 완전히 다른 분포로 이동 → hybrid/RRF rank 도 (rank-based 라 어느 정도 완화되긴 하나) hit 의 ordering 이 V007 와 다름. 같은 chunk 가 V007 에서는 rank 3, V009 에서는 rank 7 같은 변동이 자연스러움.

§11.3 의 "search_response.v1 변경 없음 (내부 FTS 구현은 wire-invisible)" 은 schema shape level 에서는 맞음. 그러나 wire content (hits[] 의 ordering + snippet) 가 변화하는 사실은 명시되지 않음. 특히:

  1. Eval baseline regression: crates/kebab-eval/ 의 goldens.csv 의 expected chunk_id sequence 가 V007 기준이면, V009 적용 후 fail 가능. spec §11 은 eval runner 의 config_snapshot_jsonindex_version bump 를 picks up 한다고만 명시 (이는 옳음) — 그러나 baseline regenerate 책임 / 시점이 명시 안 됨. PR scope 의 일부인지 eval P5 follow-up 인지 모호.

  2. search cache (p9-fb-19) 의 corpus_revision invalidation: §5.2 가 "increment" 만 제시. 그런데 design §9 의 corpus_revision 정의 (table line 1523) 는 "ingest commit 발생 (ANY new/updated)" — V009 schema migration 은 ingest commit 이 아님. V009 migration 본체가 직접 corpus_revision 을 +1 해 주지 않으면, 다음 ingest 까지 LRU cache 가 여전히 V007 token 기반 결과를 반환할 위험. 본 spec 은 V009 migration tail 에 UPDATE kv SET v = v + 1 WHERE k = 'corpus_revision' 같은 문장의 명시가 없음.

  3. MCP / TUI / CLI 의 surface 영향 의식 부재: README.md line 88 + integrations/claude-code/kebab/SKILL.md line 63 모두 V007 의 substring 매칭 + 3-char hint 를 user-facing 표현으로 명시. V009 는 두 표현 모두 stale (substring 매칭 없어짐 + 2-char query 작동). 본 spec §7.3 은 short_query_hint 의 "제거 또는 조건 변경" 만 vague 하게 언급 — README

    • SKILL.md + HANDOFF.md 의 정확한 갱신 범위 미정.
  • Suggested fix:
    • §11.3 갱신: "wire schema 의 shape 은 unchanged 하나, hit ordering / snippet 내용은 V007 와 다름. eval golden baseline 재생성 PR scope 에 포함."
    • §5.2 갱신: V009 migration 의 마지막 statement 로 UPDATE kv … corpus_revision 명시. (또는 첫 ingest 가 자동으로 bump 한다는 정책을 근거로 의도적 생략 명시.)
    • §7.3 갱신: short_query_hint 의 운명 명확화 (제거 권장 — 2-char 가 유효 query 가 됨). README.md / SKILL.md / HANDOFF.md 의 갱신 범위 명시 (CLAUDE.md §wire schema cascade 의 "shipped integration 동시 갱신" rule 에 따른 SKILL.md 갱신은 필수).

Finding #8: disable_korean_morphological config 노브의 surface + cascade 정의 누락 (MAJOR)

  • Severity: major
  • Location: §6.3 (line 193)
  • Issue:

§6.3 마지막 줄:

Advanced user 는 config.toml [rag]disable_korean_morphological = true 로 opt-out (legacy unicode61 fallback, V009 migration 후에도 원문 text 만 FTS index).

다음이 모두 미정:

  1. Config schema 추가: kebab-config crate 의 [rag] section 정의 + default value + serde 처리. 갱신 PR scope 인지 별 PR 인지.
  2. 위치 적합성: [rag] 보다는 [search] 또는 [index.fts] 가 semantic 자연 — [rag] 는 retrieval-augmented generation 측 (LLM / prompt) 노브. 명명 review 필요.
  3. cascade 의미: opt-out 시 새 chunk INSERT 도 tokenized_korean_text = NULL → 한국어 2-char query 의 hit 가 사라짐. 사용자 기대는 "V007 의 trigram substring 매칭으로 fallback" 일 수도, "V002 의 unicode61 만" 일 수도 있는데 spec 은 후자 (unicode61 only). 이게 의도라면 명시 + 사용자 가이드. trigram fallback 은 V009 migration 이 trigram 을 이미 drop 했으므로 불가능 — 그러면 disable 의 의미는 "한국어 lexical 으로 0-hit 으로 복귀" 에 가까움. 사용자 가치 불명확.
  4. eval runner 영향: opt-out 시 lexical_index_version 값이 달라져야 함 (예: fts5-v009-no-morpho) — 그렇지 않으면 같은 index_version 으로 두 다른 동작이 공존하여 eval baseline reproducibility 깨짐. spec 침묵.
  • Suggested fix: §6.3 의 disable 노브를 다음 중 하나로 명시화:
    • Option A (drop disable 노브): default-enabled 한 가지만 — 사용자 선택권 없음. binary 의 dict 비용은 모두 부담. simplicity 우선.
    • Option B (build-time feature 만): fts_korean_morphological cargo feature 로 build-time off → release binary 별도 컷 (영문 전용 사용자용). runtime 노브 제거. 명료성 ↑.
    • Option C (현재 spec 유지하되 보강): config 위치 명확화 ([search] 권장), default 명시, opt-out 시 index_version suffix 명시, eval config_snapshot 의 영향 명시.

Finding #9: lindera 의 license + dict source 의 검증 evidence 부재 (MAJOR)

  • Severity: major
  • Location: §6.1 (line 175-176), §10.1 (line 308-312)
  • Issue:

§6.1 + §10.1 모두 "lindera = MIT/Apache-2.0 dual", "lindera-dict-ko-dic = Apache-2.0 (Korean dict, Google search engine dict 기반)" 라 단정. evidence / 참조 URL / commit SHA 없음.

  • ko-dic 은 MeCab-ko-dic (KAIST 기반) 의 fork 인 경우가 많고, 라이센스는 Creative Commons (CC BY-SA) + Apache-2.0 dual 또는 별 라이센스 가능성. spec 의 "Google search engine dict 기반" 표현은 출처 불분명 — Google 사전이 별도로 존재하지 않거나 misattribution 가능.

  • kebab workspace 의 cargo deny / licenses.toml 의 allow-list 갱신 PR scope 인지 침묵. Apache-2.0 만 dual-licensed 된 dict 가 아니면 reject 위험.

  • Suggested fix: §10.1 에 다음 evidence 추가:

    • lindera crate 의 정확한 라이센스 SPDX (예: MIT OR Apache-2.0) + Cargo.toml license field 인용.
    • lindera-dict-ko-dic 의 정확한 SPDX + GitHub repo URL + dict 의 upstream source (예: MeCab-ko-dic 의 commit) cross-link.
    • workspace 의 deny.toml license allow-list 갱신 필요 여부 (Apache-2.0 이 이미 allow 면 OK, CC BY-SA 면 추가 필요).

Stylistic / clarity findings (no rewrite required)

  • §1 Summary 의 "기존 trigram 의 장점 (영어 substring 매칭, 부분 매칭 지원) 을 보존" — finding #1 의 contradiction 기원이므로 같은 PR 에서 표현 정리.
  • §4.1 표의 "Migration cascade" row 의 Option A = "V009 (index_version bump)" — index_version 만 bump 인지, corpus_revision / schema_version 도 같이 bump 인지 모호. design §9 표 와 매핑 명시 권장.
  • §2.1 line 22 "trigram bucket 이 없어" — trigram tokenizer 가 "bucket" 이라는 용어를 안 쓰므로 "3-character gram 의 최소 길이 미만" 같은 표현 권장. Reader 가 SQLite FTS5 docs 와 직접 비교 가능.
  • §6.3 "default 로 feature 포함" — cargo feature 의 default 처리 방식 (예: [features] default = ["fts_korean_morphological"]) 의 정확한 표기 권장.
  • §11.1 의 index_version 문자열 "fts5-v009-korean-morphological" 의 source-of-truth 위치 (예: kebab_store_sqlite::FTS_INDEX_VERSION 상수 또는 lexical_index_version() 함수) 명시. kebab_store_vector::INDEX_VERSION_STR 과 별 lexical 측 상수가 어디서 정의되는지 spec 침묵.
  • §12.1 Release notes 항목은 사용자 도그푸딩 영향에 영향이 큼에도 표현이 bullet point 1줄씩 — CLAUDE.md §Release / binary version bump 의 "친절하고 자세하게 풀어서 설명" 정책 과 미스매치. release notes draft 단계에서 보강 권장.
  • §9.1 의 dogfood fixture KB / corpus 미명시 (Finding #7 의 일부) — minor rewording 으로 reproducibility 명시 가능.

Verdict rationale

세 가지 critical finding (English substring 회귀의 self-contradiction, 기존 KB backfill 의 silent breakage, unicode61 + lindera 의 sub-morpheme 매칭 보장 부재) 와 여섯 가지 major finding (CI guard rename, trigger race, storage evidence, ordering / cache invalidation, config 노브, license evidence) 가 동시에 존재. 이 중 finding #1 / #2 는 spec 의 self-contradiction 이라 implementation 시점에서 발견 시 design 자체를 재논의해야 함 — round 1c 단계에서 spec 갱신이 비용 효율적.

minor finding 들만이면 ACCEPT 가능했으나, critical 의 존재로 NEEDS_REWRITE.


다음 7 가지를 같은 commit 의 spec rewrite 에 포함 권장:

  1. §3 + §4.1 + §9.2: English regression 명시unicode61 의 whole-token 매칭으로 환원 사실 인정. Non-Goals 의 "trigram 의 substring 매칭 유지" 조항 삭제 또는 별 column dual-tokenize 설계 추가. §9.2 의 fts_v009_english_substring_retained test 를 의도에 맞게 rename + 재작성 (또는 dual-tokenize 채택 시 그대로 유지).
  2. §5.1 + §8.2 + §9.1 + §12.1: 기존 KB backfill 정책 결단 — eager backfill (V009 migration 또는 first-boot hook) 채택 명시, 또는 lazy 명시
    • release notes / AC 표현 일치. 둘 다 채택 시 trade-off matrix 추가.
  3. §6.2 + §9.1: lindera ko-dic segmentation evidence — spike branch 결과를 appendix 로 첨부 ('한국어', '서울특별시', '지하철은 빠르다', '한국문화는오래되었다', 'rust 최적화' 5-6 fixture 의 실제 tokenize output). AC §9.1 의 hit 보장 이 fixture 와 일치하는지 cross-check.
  4. §5.3 + §6.2: CI diff-check 및 trigger semantics 명시 — V007 verbatim test 의 rename / replace / delete 중 선택. trigger 의 CASE expression 포함한 verbatim block 의 design 측 갱신 범위 명시. ingest pipeline 의 "lindera tokenize + chunk INSERT 단일 transaction" invariant 추가.
  5. §4.1 + §10.2 + §10.3: 비용 evidence — dict size, binary delta, SQLite file delta, ingest latency delta 의 실측 첨부.
  6. §11 + §7.3 + Surface cascade: corpus_revision bump 의 V009 migration tail 명시. eval golden 재생성 책임 명시. short_query_hint 의 운명 명시 (제거 권장). README.md / integrations/claude-code/kebab/SKILL.md / HANDOFF.md 갱신 범위를 PR scope 에 explicit list.
  7. §6.3: config 노브 정리disable_korean_morphological 의 위치 / default / disable 시의 fallback 의미 / lexical_index_version 영향 명시. 또는 노브 자체를 spec 에서 삭제 (Option A).

위 7 항목이 closure 되면 critic round 2 에서 ACCEPT 가능.