v3.4.1 — 전체 시스템 버그 감사 (data-loss/race/파싱 강건성)
v3.4.1 — 전체 시스템 버그 감사 (data-loss / race / 파싱 강건성)
Summary
8천 LOC 전반을 다중 에이전트로 감사(find → 적대적 verify)해 39건 후보 중 28건을
확정하고, 그중 23건을 수정(회귀 테스트 동반, 전체 599 passed). 핵심은 임베딩
서버 일시 장애·동시 write·LLM 출력 파싱이 조용한 영구 데이터 유실로 번지던
경로들을 닫은 것.
Fixed
동시성 / DB (HIGH·MEDIUM)
- WAL/busy_timeout 미설정 (
indexer.open_db):index.db가 기본 DELETE 모드라
동시 reader/writer 가unable to open database file(운영 370건+)·database is locked
를 빈발.PRAGMA journal_mode=WAL+busy_timeout=5000설정 (extractor_cache 선례).
WAL 은 파일 단위 영속이라 embed_cache 등 sub-conn 도 상속. - 세션 인덱서 무 lock:
trigger_background_indexer가 락 없이 detach spawn →
동시 실행 중복 작업.incremental_index에 flock(LOCK_NB) 추가 (session-indexer.lock). - alias
_save고정 tmp명 race: 동시 SessionEnd 가 같은 tmp 충돌/orphan-unlink →
alias_index손상. PID-고유 tmp 로 분리. - contradiction queue append-vs-rewrite race:
_mark_resolved가 tmp FD 에만
flock 해 read~replace 사이 concurrent append 가 유실._mark_resolved와
append_to_review_queue양측이 공용<queue>.lock을 LOCK_EX 로 잡아 직렬화
(v3.4.0 의 "single-writer 가정" 한계 중 append-loss 경로 개선).
임베딩 서버 일시 장애 → 영구 누락 (HIGH·MEDIUM)
- 세션 vec backfill sentinel: 서버 다운 중 backfill 이 영구 빈-blob sentinel 을
박아 복구 후에도 세션이 semantic recall 에서 영구 제외.EmbedUnavailable로
'본문 없음'(영구 skip)과 '서버 장애'(재시도)를 분리. - 메모리 vec 재인덱싱 mtime: embed 실패 시에도 mtime 을 갱신해 다음 run 이 skip,
메모리가 영구히 vec 없이 남음. embed 실패면 mtime/FTS/vec 미갱신 → 다음 run 재시도.
(retry/backoff 는 embed_text 가 recall 핫패스 공용이라 미추가 — mtime-skip 이 안전한
재시도 형태.)
데이터 유실 가드 (HIGH·MEDIUM·LOW)
- dedup merge: Gemma 통합 실패 시에도 non-canonical 삭제(ok:true 위장). 통합
성공한 파일만 삭제하고, 전부 실패/본문 빈 경우 overwrite·삭제 없이 중단. - dedup canonical overwrite: name/description/type 만 다시 써서 supersedes/
deprecated_by audit link 소실. 나머지 frontmatter flow-style 보존 재방출. - dedup
_backup: 기존.bak무조건 덮어쓰기 → 직전 복구본 소실..bak존재 시
타임스탬프 회전. - extractor 부정 캐시: Gemma 호출 실패 빈 결과를 영구 캐시 → 복구 후 추출 영구
스킵. 호출 실패(out is None)면 빈 결과 캐시 안 함. 정상 빈 응답("[]")은 캐시. - turns_cache wipe: append-only jsonl 의 일시 read 실패([] 반환)가 기존 turns 를
삭제+mtime 갱신해 영구 소실. st_size>0 + 기존 turn 존재면 이번 회차 건너뛰고 재시도.
파싱 강건성 (MEDIUM·LOW)
- staged frontmatter 줄바꿈: LLM 값(title/reason/evidence)의 줄바꿈/
---이
frontmatter 구조를 깨거나 조기 종료._fm_oneline으로 단일 라인 정규화 (따옴표
미사용 — naive 파서 호환). promote 경로(cmd_approve) 영구 메모리 write 에도 적용. - contradiction scalar-dup:
_patch_frontmatter_list가 scalarkey: value를
flow/block 어느 정규식에도 못 잡아 중복 key append(YAML 손상). scalar 감지 후 거부. - extractor greedy JSON:
re.search(r"\[[\s\S]*\]")가 배열 밖 산문 대괄호까지
삼켜 후보 폐기. 코드펜스 strip 후 직접 파싱 1순위 + balanced [...] 후보 선택. - alias frontmatter 따옴표:
description: "x"의 따옴표가 alias 프롬프트로 유입.
양끝 짝 따옴표만 제거.
훅 동작 (MEDIUM·LOW)
- 서브에이전트 SessionStart 게이팅: agent_type 미게이팅으로 모든 서브에이전트
시작마다 동기 요약 생성이 블로킹. agent_type 있으면 즉시 종료 (메인 세션 무영향). - alias claude subprocess recursion guard: provider="claude" 시 자식 claude 의
훅 재귀. subprocess env 에MV3_HOOK_RECURSION_GUARD=1주입. - SessionStart MISS 경로 staged 청소 누락: HIT 경로만
purge_staged_memory()
호출하던 것을 양쪽 모두로. - turns_cache dead import:
self_eval(flat-deploy 미배포) import 를 guarded 하여
배포 환경에서 import-time 폭발 방지. - recall alias 후보 게이트 통과: alias fallback 후보(score=0)가 score_threshold
게이트에서 탈락해 "임베딩 약한 메모리 alias 복구" 무력화. normalize 후 게이트 직상
sentinel 부여 (raw_cosine 정렬이 최종 ranking 결정 — 순위 왜곡 없음).
Test count
533 (v3.4.0) → 599 (+66, 회귀 테스트 동반)
Migration
bash install.sh 재실행. 스키마 변경 없음. WAL 은 인덱서 첫 실행에서 index.db 를
1회 전환(이후 모든 conn 상속), -wal/-shm 사이드카 파일이 ~/.claude/mindvault-v3/
에 생긴다 (로컬 FS 라 무해).
Known limitations (현재 상태 — 미수정 항목과 사유)
- recall
score_threshold게이트 vs 정렬 키 불일치 (recall-hot-path-1): 게이트는
normalized score 기준인데 정렬은 raw_cosine 우선이라, score 는 낮지만 raw_cosine 이
가장 강한 후보가 게이트에서 탈락할 수 있다. alias 후보 무력화(-5)는 이번에 sentinel
로 고쳤으나, 일반 게이트 동작은 그대로 둔다 —SCORE_THRESHOLD=0.50이 현재
게이트 의미에 맞춰 hit rate 66.3% 로 튜닝된 값이라, 정렬·게이트 키를 통일하려면
recall eval 로 FP rate 재측정이 선행돼야 한다 (사용자 결정으로 보류). - SIGALRM(ITIMER_REAL) 하드 타임아웃 (recall-hot-path-2/3):
recall_memory전체
구간이 알람 무장 상태라, 드물게 pathlib/sqlite 비-재진입 구간을 인터럽트하면 객체가
반파괴될 수 있다 (PosixPath '_str'에러, 운영 1건). 단, 이 알람은 400ms wall-clock
하드월의 유일한 강제 수단이고 thread/subprocess 격리 재설계는 핫패스 회귀 위험이
과도해 그대로 둔다. 실패는 이미 graceful(FATAL 로그 후 빈 회수). - supersede
_apply_supersedehalf-state (contradiction-halfstate-5): 두 파일
순차 mutation 중 NEW write 가 OLD 성공 후 실패하면 OLD 만 deprecated 인 좁은 윈도우.
재시도가 idempotent 로 수렴하며, 제안된 both-tmp 방식도 윈도우를 제거 못 하고 축소만
하므로 그대로 둔다 (exit 2 + stderr 로 surfaced). - (v3.4.0 의 supersede audit-trace stale, backfill
--memory-dir범위 한계는 그대로.)
Memory ops
- 신규 lock 파일:
~/.claude/mindvault-v3/session-indexer.lock,
~/.claude/mindvault-v3/contradictions.jsonl.lock(둘 다 flock advisory).