# 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 는 이 모순을 그대로 노출:

```rust
#[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 의 다음 두 문장 사이에 직접 모순:

> 2. `tokenized_korean_text` 는 초기에 NULL (기존 chunks 에 형태소 분해 불가).
> 3. **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 가 즉시 fail** —
`migrations/V007__fts_trigram.sql` 의 `tokenize='trigram'` 이 design 의
`tokenize='unicode61'` 와 안 맞으므로.

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

1. **Rename + replace**: `fts_v007_matches_design_section_5_5_verbatim` 를
   `fts_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_json` 이 `index_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.

---

## Recommended r1c rewrite scope

다음 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 가능.
