feat(p3-2): fastembed-adapter — kb-embed-local 크레이트 + FastembedEmbedder #15

Merged
altair823 merged 1 commits from feat/p3-2-fastembed-adapter into main 2026-05-01 08:42:08 +00:00
Owner

변경 요약

P3-2 fastembed-adapter 작업입니다. 새 크레이트 kb-embed-local를 추가해서 P3-1에서 정의한 Embedder trait의 첫 번째 실제 구현을 제공합니다. fastembed-rs (ONNX runtime)을 통해 multilingual-e5-small (384 dims) 모델을 로컬에서 실행합니다.

무엇을 했는가

핵심 동작

  • Pre-load dim 검증: TextEmbedding::get_model_info로 모델 메타만 조회한 뒤 config.models.embedding.dimensions와 비교. 불일치 시 ~470MB ONNX 초기화를 시작하기 전에 bail합니다.
  • e5 prefix: tokenization 직전에 EmbeddingKind::Document"passage: ", Query"query: "을 prepend. 단위 테스트로 정확한 prefix 문자열을 pin.
  • 배치 처리: 입력을 config.models.embedding.batch_size 단위로 chunk하고 결과를 입력 순서대로 concat.
  • L2 정규화: fastembed 4.9의 default transformer pipeline이 이미 정규화하므로 (검증: fastembed/src/text_embedding/output.rs:43) adapter에서는 재정규화하지 않습니다. 통합 테스트가 ‖v‖ ≈ 1.0 ± 1e-3을 pin해서 향후 fastembed bump이 이 invariant을 깨면 즉시 fail합니다.
  • 첫 실행 다운로드 가시화: TextEmbedding::try_new 전후로 tracing::info! 두 번. 470MB을 30-60초간 침묵 속에서 받지 않게 됩니다.
  • {data_dir} / ${XDG_DATA_HOME} / ~ 경로 expansion: kb-store-sqlite와 동일한 패턴을 in-crate에 hand-rolled. kb-config의 public API를 건드리지 않습니다 — duplication은 spec authorization 없이는 곤란하다는 판단. 후속 작업으로 promote 후보입니다.
  • 동기 실행: trait가 sync. ONNX session 호출은 Mutex<TextEmbedding>로 직렬화. ORT Session 자체는 Send+Sync지만 caller (kb-app indexer)가 어차피 sequential batch라 보수적 선택입니다 — 프로파일링에서 contention이 보이면 P3-3+에서 재검토.

테스트

  • default 라인 (11건): 모델 로드 없이 cheap branches만 — check_dim 일치/불일치, prefix_input Document/Query/empty, resolve_model known/unknown, expand_path 4가지 케이스 (substitution, no-op, XDG 설정, XDG 미설정 → ~/.local/share 재귀 확장). edition 2024가 set_var/remove_var을 unsafe로 만들어서 XDG 테스트는 정적 Mutex + RAII guard로 직렬화합니다.
  • #[ignore] 통합 라인 (7건): 실제 모델을 로드해야 하는 spec 시나리오 모두 — default config 생성, dim mismatch belt-and-braces, Document vs Query cosine differential, L2 unit norm, byte-equal determinism, batch-64 성능 < 5s, 5-문장 multilingual fixture snapshot.
  • Snapshot 베이스라인 정책: SNAPSHOT_HASH_BASELINE = 0은 silent pass가 아니라 panic을 발생시킵니다. 첫 --ignored 실행에서 측정값과 paste-back 가이드를 출력하고 명시적으로 fail — maintainer가 보지 못하고 지나가는 케이스를 차단합니다.
  • 워크스페이스 default 라인 222건 통과. cargo clippy --workspace --all-targets -- -D warnings clean.

의존성

Allowed deps 준수: kb-config, kb-embed (kb-core trait 표면 재노출), fastembed = \"4.9\", tracing, anyhow (trait 반환 타입이 강제). tokenizers/ort는 fastembed 통해 transitive로만 들어옵니다 — direct dep로 등록하면 unused 경고. reqwest/hyper/hf-hub도 transitive (모델 다운로드는 fastembed 책임 — spec carve-out).

kb-core direct dep와 thiserror는 둘 다 unused로 판명되어 제거했습니다 (kb-embed이 trait 표면을 그대로 재노출하므로 kb-core 직접 의존 불필요, error 처리는 anyhow만).

fastembed는 4.x 라인에 pin했습니다. 5.x가 stable로 나와 있지만 P3-3 (lancedb-store)이 embedder output 형태를 consume할 때 같이 보는 게 안전하다고 판단 — bump은 그때.

Forbidden deps (kb-source-fs, kb-parse-md, kb-normalize, kb-chunk, kb-store-*, kb-search, kb-llm*, kb-rag, kb-tui, kb-desktop) 어느 것도 cargo tree -p kb-embed-local에 등장하지 않습니다.

변경 파일

  • crates/kb-embed-local/Cargo.toml (신규)
  • crates/kb-embed-local/src/lib.rs (신규 — FastembedEmbedder + helpers)
  • crates/kb-embed-local/tests/embed_model.rs (신규 — 11 default + 7 ignored)
  • crates/kb-embed-local/tests/fixtures/embed/known-sentences.json (신규)
  • Cargo.toml (workspace member 등록 + fastembed = \"4.9\")
  • Cargo.lock

후속 작업 후보

  • expand_path을 kb-config의 public API로 promote — 현재 kb-store-sqlite와 kb-embed-local 두 곳에 같은 helper가 hand-rolled됨. 별도 spec 변경이 필요해서 본 PR 범위 밖.
  • Mutex<TextEmbedding> → bare Arc<TextEmbedding> 전환. ORT Session이 Send+Sync라 가능하지만 contention이 실제로 발생하는지 확인 후 P3-3+에서.
  • fastembed = \"5\" bump — P3-3 진입 시점에 같이 검토.
  • snapshot baseline pin — 첫 CI --ignored lane에서 panic 메시지의 hash을 PR로 받아 const에 paste.
  • multi-arch snapshot 안정성 — Linux x86_64와 macOS arm64의 ONNX kernel 차이로 hash가 다를 수 있음. 다중 CI 라인이 들어오면 per-arch baseline 또는 더 넓은 tolerance.

Out of scope

  • Reranker (P+).
  • 다른 모델 provider (Ollama embedding endpoint, candle) — 별도 adapter 크레이트.
  • Visual / image embeddings (P6).

design §7.2, §6.4, §9 / 보고서 §11.3 참고.

## 변경 요약 P3-2 fastembed-adapter 작업입니다. 새 크레이트 `kb-embed-local`를 추가해서 P3-1에서 정의한 `Embedder` trait의 첫 번째 실제 구현을 제공합니다. fastembed-rs (ONNX runtime)을 통해 `multilingual-e5-small` (384 dims) 모델을 로컬에서 실행합니다. ## 무엇을 했는가 ### 핵심 동작 - **Pre-load dim 검증**: `TextEmbedding::get_model_info`로 모델 메타만 조회한 뒤 `config.models.embedding.dimensions`와 비교. 불일치 시 ~470MB ONNX 초기화를 시작하기 전에 bail합니다. - **e5 prefix**: tokenization 직전에 `EmbeddingKind::Document`은 `"passage: "`, `Query`는 `"query: "`을 prepend. 단위 테스트로 정확한 prefix 문자열을 pin. - **배치 처리**: 입력을 `config.models.embedding.batch_size` 단위로 chunk하고 결과를 입력 순서대로 concat. - **L2 정규화**: fastembed 4.9의 default transformer pipeline이 이미 정규화하므로 (검증: `fastembed/src/text_embedding/output.rs:43`) adapter에서는 재정규화하지 않습니다. 통합 테스트가 `‖v‖ ≈ 1.0 ± 1e-3`을 pin해서 향후 fastembed bump이 이 invariant을 깨면 즉시 fail합니다. - **첫 실행 다운로드 가시화**: `TextEmbedding::try_new` 전후로 `tracing::info!` 두 번. 470MB을 30-60초간 침묵 속에서 받지 않게 됩니다. - **{data_dir} / ${XDG_DATA_HOME} / ~ 경로 expansion**: kb-store-sqlite와 동일한 패턴을 in-crate에 hand-rolled. kb-config의 public API를 건드리지 않습니다 — duplication은 spec authorization 없이는 곤란하다는 판단. 후속 작업으로 promote 후보입니다. - **동기 실행**: trait가 sync. ONNX session 호출은 `Mutex<TextEmbedding>`로 직렬화. ORT Session 자체는 Send+Sync지만 caller (kb-app indexer)가 어차피 sequential batch라 보수적 선택입니다 — 프로파일링에서 contention이 보이면 P3-3+에서 재검토. ### 테스트 - **default 라인 (11건)**: 모델 로드 없이 cheap branches만 — `check_dim` 일치/불일치, `prefix_input` Document/Query/empty, `resolve_model` known/unknown, `expand_path` 4가지 케이스 (substitution, no-op, XDG 설정, XDG 미설정 → `~/.local/share` 재귀 확장). edition 2024가 `set_var`/`remove_var`을 unsafe로 만들어서 XDG 테스트는 정적 Mutex + RAII guard로 직렬화합니다. - **`#[ignore]` 통합 라인 (7건)**: 실제 모델을 로드해야 하는 spec 시나리오 모두 — default config 생성, dim mismatch belt-and-braces, Document vs Query cosine differential, L2 unit norm, byte-equal determinism, batch-64 성능 < 5s, 5-문장 multilingual fixture snapshot. - **Snapshot 베이스라인 정책**: `SNAPSHOT_HASH_BASELINE = 0`은 silent pass가 아니라 panic을 발생시킵니다. 첫 `--ignored` 실행에서 측정값과 paste-back 가이드를 출력하고 명시적으로 fail — maintainer가 보지 못하고 지나가는 케이스를 차단합니다. - 워크스페이스 default 라인 222건 통과. `cargo clippy --workspace --all-targets -- -D warnings` clean. ### 의존성 Allowed deps 준수: `kb-config`, `kb-embed` (kb-core trait 표면 재노출), `fastembed = \"4.9\"`, `tracing`, `anyhow` (trait 반환 타입이 강제). `tokenizers`/`ort`는 fastembed 통해 transitive로만 들어옵니다 — direct dep로 등록하면 unused 경고. `reqwest`/`hyper`/`hf-hub`도 transitive (모델 다운로드는 fastembed 책임 — spec carve-out). `kb-core` direct dep와 `thiserror`는 둘 다 unused로 판명되어 제거했습니다 (`kb-embed`이 trait 표면을 그대로 재노출하므로 `kb-core` 직접 의존 불필요, error 처리는 `anyhow`만). `fastembed`는 4.x 라인에 pin했습니다. 5.x가 stable로 나와 있지만 P3-3 (lancedb-store)이 embedder output 형태를 consume할 때 같이 보는 게 안전하다고 판단 — bump은 그때. Forbidden deps (`kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop`) 어느 것도 `cargo tree -p kb-embed-local`에 등장하지 않습니다. ## 변경 파일 - `crates/kb-embed-local/Cargo.toml` (신규) - `crates/kb-embed-local/src/lib.rs` (신규 — `FastembedEmbedder` + helpers) - `crates/kb-embed-local/tests/embed_model.rs` (신규 — 11 default + 7 ignored) - `crates/kb-embed-local/tests/fixtures/embed/known-sentences.json` (신규) - `Cargo.toml` (workspace member 등록 + `fastembed = \"4.9\"`) - `Cargo.lock` ## 후속 작업 후보 - `expand_path`을 kb-config의 public API로 promote — 현재 kb-store-sqlite와 kb-embed-local 두 곳에 같은 helper가 hand-rolled됨. 별도 spec 변경이 필요해서 본 PR 범위 밖. - `Mutex<TextEmbedding>` → bare `Arc<TextEmbedding>` 전환. ORT Session이 Send+Sync라 가능하지만 contention이 실제로 발생하는지 확인 후 P3-3+에서. - `fastembed = \"5\"` bump — P3-3 진입 시점에 같이 검토. - snapshot baseline pin — 첫 CI `--ignored` lane에서 panic 메시지의 hash을 PR로 받아 const에 paste. - multi-arch snapshot 안정성 — Linux x86_64와 macOS arm64의 ONNX kernel 차이로 hash가 다를 수 있음. 다중 CI 라인이 들어오면 per-arch baseline 또는 더 넓은 tolerance. ## Out of scope - Reranker (P+). - 다른 모델 provider (Ollama embedding endpoint, candle) — 별도 adapter 크레이트. - Visual / image embeddings (P6). design §7.2, §6.4, §9 / 보고서 §11.3 참고.
altair823 added 1 commit 2026-05-01 08:40:32 +00:00
First real Embedder implementation. Wraps fastembed-rs (ONNX runtime)
with the e5 prefix convention, batching, and {data_dir}/${XDG_DATA_HOME}
template expansion so model files land under config.storage.model_dir/
fastembed/ without polluting kb-config's public API.

Public surface:
- pub struct FastembedEmbedder
- pub fn new(config: &Config) -> Result<Self>
- impl kb_core::Embedder (via kb-embed re-export)

Behavior:
- Default model multilingual-e5-small (384 dims). model_id and
  model_version come from config.models.embedding.{model,version}.
- Pre-load dim check via TextEmbedding::get_model_info: dim mismatch
  bails before paying the ~470MB ONNX init cost.
- e5 prefix applied BEFORE tokenization: "passage: " for
  EmbeddingKind::Document, "query: " for EmbeddingKind::Query. Pinned
  by prefix_input unit tests.
- Batches inputs into chunks of config.models.embedding.batch_size,
  concatenates results in input order.
- L2 normalization is performed by fastembed 4.9's default transformer
  pipeline (verified at fastembed/src/text_embedding/output.rs:43);
  we skip re-normalization. Integration test pins ‖v‖ ≈ 1.0 ± 1e-3 so
  a future fastembed bump that drops this invariant fails loudly.
- Synchronous (no async runtime). Mutex serializes calls into the
  underlying ONNX session — conservative; ORT Session is Send+Sync but
  callers (kb-app indexer) batch sequentially anyway. Revisit if
  profiling shows contention.
- First-run model download surfaces via tracing::info before/after
  TextEmbedding::try_new — users no longer stare at a silent 30-60s
  pause during the 470MB pull.

Tests:
- 11 default-lane tests covering: check_dim match/mismatch (no model
  load), prefix_input Document/Query/empty, resolve_model
  known/unknown, expand_path substitution + no-op + XDG_DATA_HOME set
  + XDG_DATA_HOME unset (falls back to ~/.local/share with recursive
  ~ expansion). XDG tests serialize on a Mutex + RAII guard since
  edition 2024 makes set_var/remove_var unsafe.
- 7 #[ignore] integration tests covering: full construction with
  default config, dim-mismatch belt-and-braces, Document vs Query
  cosine differential, L2 unit norm, byte-equal determinism, batch-64
  performance under 5s, snapshot-hash stability over a 5-sentence
  multilingual fixture.
- Snapshot test fails LOUDLY when SNAPSHOT_HASH_BASELINE is 0 — prints
  the captured hash and panics with paste-back instructions, so first
  --ignored run forces the maintainer to pin the baseline rather than
  silently passing.
- Workspace: 222 tests pass (default lane); clippy clean.

Allowed deps respected: kb-config, kb-embed (re-exports kb-core
trait surface), fastembed = "4.9", tracing, anyhow. tokenizers and
ort enter transitively through fastembed; reqwest/hyper/hf-hub also
transitive (model download is fastembed's responsibility per spec
carve-out). No direct kb-core dep needed — re-exports cover it.

Pinned to fastembed 4.x rather than the recent 5.x to limit blast
radius; consider bump when p3-3 (lancedb-store) consumes the embedder
output shape.

Out of scope: reranker (P+), Ollama embedding endpoint, candle
adapter, image embeddings (P6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 reviewed 2026-05-01 08:41:13 +00:00
claude-reviewer-01 left a comment
Member

P3-2 코드 리뷰 — 셀프 머지 게이트로 인해 COMMENT only.

spec compliance 리뷰 결과 BLOCKER / MUST-FIX 모두 0건, 1건의 medium-severity FAIL (snapshot baseline silent pass)만 있었습니다. code quality 리뷰 결과 BLOCKER 0건, MUST-FIX 3건 + IMPORTANT 3건. 모든 MUST-FIX (snapshot baseline panic, unused thiserror 제거, unused kb-core 제거) + 모든 IMPORTANT (first-run download tracing, XDG fallback 테스트 커버리지, Mutex justification 코멘트 정리)을 PR에 반영했습니다.

핵심 포인트:

  • Pre-load dim 검증이 ~470MB 다운로드 비용을 절약하는 fail-fast 경로의 본질입니다.
  • snapshot baseline = 0이 silent pass가 아니라 panic으로 fail-loud 보장 — snapshot test의 본질을 정확히 enforce.
  • L2 normalization을 fastembed에 위임하되 통합 테스트로 invariant을 pin해서 dependency drift detection.
  • XDG 테스트의 정적 Mutex + RAII guard는 edition 2024 unsafe set_var의 정답 패턴.
  • direct deps (kb-core, thiserror) 정리로 의도하지 않은 의존 그래프 확장 차단.

inline 코멘트는 모두 잘 만든 결정에 대한 노트입니다. 머지 진행해도 됩니다.

후속 작업 후보 5건 (expand_path을 kb-config로 promote, Mutex 제거 검토, fastembed 5.x bump 시점, snapshot baseline pin via CI, multi-arch snapshot stability)은 PR 본문에 정리해두었습니다.

P3-2 코드 리뷰 — 셀프 머지 게이트로 인해 COMMENT only. spec compliance 리뷰 결과 BLOCKER / MUST-FIX 모두 0건, 1건의 medium-severity FAIL (snapshot baseline silent pass)만 있었습니다. code quality 리뷰 결과 BLOCKER 0건, MUST-FIX 3건 + IMPORTANT 3건. 모든 MUST-FIX (snapshot baseline panic, unused thiserror 제거, unused kb-core 제거) + 모든 IMPORTANT (first-run download tracing, XDG fallback 테스트 커버리지, Mutex justification 코멘트 정리)을 PR에 반영했습니다. 핵심 포인트: - Pre-load dim 검증이 ~470MB 다운로드 비용을 절약하는 fail-fast 경로의 본질입니다. - snapshot baseline = 0이 silent pass가 아니라 panic으로 fail-loud 보장 — snapshot test의 본질을 정확히 enforce. - L2 normalization을 fastembed에 위임하되 통합 테스트로 invariant을 pin해서 dependency drift detection. - XDG 테스트의 정적 Mutex + RAII guard는 edition 2024 unsafe set_var의 정답 패턴. - direct deps (kb-core, thiserror) 정리로 의도하지 않은 의존 그래프 확장 차단. inline 코멘트는 모두 잘 만든 결정에 대한 노트입니다. 머지 진행해도 됩니다. 후속 작업 후보 5건 (expand_path을 kb-config로 promote, Mutex 제거 검토, fastembed 5.x bump 시점, snapshot baseline pin via CI, multi-arch snapshot stability)은 PR 본문에 정리해두었습니다.
@@ -0,0 +15,4 @@
# needed for the multilingual-e5-small path.
fastembed = { workspace = true }
tracing = { workspace = true }
anyhow = { workspace = true }

Allowed deps 목록에 있던 kb-corethiserror을 둘 다 제거한 결정이 정답입니다 — kb-embed이 trait 표면을 재노출하므로 kb-core direct dep는 redundant, error path는 anyhow만 사용. clippy unused-crate-dependencies가 잡았을 cruft를 spec compliance를 좁게 해석하지 않고 "실제로 쓰는 deps만 선언" 원칙으로 정리한 게 좋습니다.

Allowed deps 목록에 있던 `kb-core`와 `thiserror`을 둘 다 제거한 결정이 정답입니다 — `kb-embed`이 trait 표면을 재노출하므로 `kb-core` direct dep는 redundant, error path는 `anyhow`만 사용. clippy `unused-crate-dependencies`가 잡았을 cruft를 spec compliance를 좁게 해석하지 않고 "실제로 쓰는 deps만 선언" 원칙으로 정리한 게 좋습니다.
@@ -0,0 +82,4 @@
.context("fastembed: get_model_info")?;
check_dim(model_info.dim, config.models.embedding.dimensions)?;
tracing::info!(

Pre-load dim 검증이 핵심입니다. TextEmbedding::get_model_info가 ONNX session 초기화 없이 모델 메타만 정적으로 반환하는 점 (fastembed 4.9.1 기준 검증됨)을 활용해서 ~470MB 다운로드 + ONNX init를 시작하기 전에 bail합니다. 사용자가 dim 설정을 잘못 적었을 때 "커피 한 잔 마시고 와서 fail"이 아니라 즉시 fail하는 UX 차이가 큽니다.

Pre-load dim 검증이 핵심입니다. `TextEmbedding::get_model_info`가 ONNX session 초기화 없이 모델 메타만 정적으로 반환하는 점 (fastembed 4.9.1 기준 검증됨)을 활용해서 ~470MB 다운로드 + ONNX init를 시작하기 전에 bail합니다. 사용자가 dim 설정을 잘못 적었을 때 "커피 한 잔 마시고 와서 fail"이 아니라 즉시 fail하는 UX 차이가 큽니다.
@@ -0,0 +102,4 @@
model = %config.models.embedding.model,
cache_dir = %cache_dir.display(),
"loading embedding model (first run will download ~470MB)"
);

첫 실행 다운로드 가시화. show_download_progress: false로 fastembed 자체 progress bar는 끄고 그 대신 tracing::info! 두 번 (load 시작 시점에 "~470MB will download" 명시 + 성공 후 dimensions와 함께 confirm)으로 깔끔하게 처리한 게 정답입니다. progress bar는 stdout 파괴적이라 CLI 출력 포맷을 망가뜨리는데, tracing은 user가 RUST_LOG로 통제할 수 있어 production에서 안정적입니다.

첫 실행 다운로드 가시화. `show_download_progress: false`로 fastembed 자체 progress bar는 끄고 그 대신 `tracing::info!` 두 번 (load 시작 시점에 "~470MB will download" 명시 + 성공 후 dimensions와 함께 confirm)으로 깔끔하게 처리한 게 정답입니다. progress bar는 stdout 파괴적이라 CLI 출력 포맷을 망가뜨리는데, tracing은 user가 RUST_LOG로 통제할 수 있어 production에서 안정적입니다.
@@ -0,0 +137,4 @@
}
fn embed(&self, inputs: &[EmbeddingInput<'_>]) -> Result<Vec<Vec<f32>>> {
if inputs.is_empty() {

L2 정규화는 fastembed 4.9의 default transformer가 이미 한다는 점을 코드 코멘트와 검증 path 둘 다로 박아둔 게 좋습니다. 통합 테스트 output_vectors_are_l2_normalized‖v‖ ≈ 1.0 ± 1e-3을 pin해서 향후 fastembed bump이 normalize를 빼면 즉시 fail합니다. "외부 라이브러리가 알아서 하리라"는 가정을 테스트로 못 박는 정확한 패턴입니다.

L2 정규화는 fastembed 4.9의 default transformer가 이미 한다는 점을 코드 코멘트와 검증 path 둘 다로 박아둔 게 좋습니다. 통합 테스트 `output_vectors_are_l2_normalized`가 `‖v‖ ≈ 1.0 ± 1e-3`을 pin해서 향후 fastembed bump이 normalize를 빼면 즉시 fail합니다. "외부 라이브러리가 알아서 하리라"는 가정을 테스트로 못 박는 정확한 패턴입니다.
@@ -0,0 +412,4 @@
let _lock = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let _guard = XdgGuard::capture();
// SAFETY: lock held for the duration of this test.
unsafe { std::env::set_var("XDG_DATA_HOME", "/custom/path") };

XDG 테스트의 직렬화 패턴 — 정적 Mutex + RAII guard로 환경변수 snapshot/restore. edition 2024가 set_var/remove_var을 unsafe로 분류한 이유가 정확히 이것 (다른 스레드가 환경변수를 동시 read하면 UB)인데, ENV_LOCK로 cross-test 직렬화 + XdgGuard로 prior value 복원하는 두 단계 모두 갖췄습니다. 향후 환경변수를 건드리는 테스트가 같은 ENV_LOCK을 공유하도록 확장 가능한 구조이기도 합니다.

XDG 테스트의 직렬화 패턴 — 정적 Mutex + RAII guard로 환경변수 snapshot/restore. edition 2024가 `set_var`/`remove_var`을 unsafe로 분류한 이유가 정확히 이것 (다른 스레드가 환경변수를 동시 read하면 UB)인데, `ENV_LOCK`로 cross-test 직렬화 + `XdgGuard`로 prior value 복원하는 두 단계 모두 갖췄습니다. 향후 환경변수를 건드리는 테스트가 같은 ENV_LOCK을 공유하도록 확장 가능한 구조이기도 합니다.
@@ -0,0 +267,4 @@
// so we hard-fail until a maintainer commits the pin. Both
// hex (paste-friendly) and decimal forms are printed.
eprintln!(
"kb-embed-local snapshot baseline (paste into SNAPSHOT_HASH_BASELINE): \

Snapshot baseline 정책이 정확합니다. SNAPSHOT_HASH_BASELINE = 0이 silent pass가 아니라 panic — 측정값을 출력하고 "paste back into the const" 가이드를 함께 띄웁니다. snapshot test의 본질이 "pin할 때까지 fail해야 의미가 있다"인데 그 invariant이 실제로 enforce됩니다. 일반적인 "if baseline == 0 then return" 패턴의 함정 (한 번도 진짜 검증되지 않은 채 green 유지)을 정확히 피했습니다.

Snapshot baseline 정책이 정확합니다. `SNAPSHOT_HASH_BASELINE = 0`이 silent pass가 아니라 panic — 측정값을 출력하고 "paste back into the const" 가이드를 함께 띄웁니다. snapshot test의 본질이 "pin할 때까지 fail해야 의미가 있다"인데 그 invariant이 실제로 enforce됩니다. 일반적인 "if baseline == 0 then return" 패턴의 함정 (한 번도 진짜 검증되지 않은 채 green 유지)을 정확히 피했습니다.
altair823 merged commit 9beef930b4 into main 2026-05-01 08:42:08 +00:00
altair823 deleted branch feat/p3-2-fastembed-adapter 2026-05-01 08:42:09 +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#15