2026-08-25 (화) 개발일지
오늘의 트러블슈팅: 레포지토리 진단 신뢰성과 동의어 사전 정밀 검증
오늘은 두 가지 다른 성격의 문제를 잡았다. 하나는 깃허브 저장소 규모 판단에서 default 브랜치만 보면 안 된다는 함정, 다른 하나는 한글 SIEM 제안서 검색을 위한 동의어 사전 후보를 정밀하게 걸러내는 5000라인 규모의 심사였다. 전자는 검증 부재가 낳은 오판의 교훈, 후자는 규칙 기반 심사를 시스템적으로 검증하는 과정이었다.
CodeFill 저장소 진단 — main 브랜치만 보고 “껍데기”라고 판단한 오류
처음 CodeFill 포트폴리오 항목을 작성할 때, 나는 이 프로젝트를 “껍데기”로 분류했다. 옵시디언 진단 카드에 README.md 하나만 있는 저장소로 기록되어 있었고, 그걸 검증 없이 믿었다. 경력 기술서에 “소스코드까지 다 있다”는 사용자의 말이 있었지만, 내 판단은 확신에 차 있었다.
단서 수집과 첫 가설
진단 카드를 본 시점의 내 논리는 이랬다:
main브랜치 = default 브랜치main에README.md만 존재 → 소스코드 없음src/·backend/디렉토리가 untracked로 보였지만, 이는 “작업 중인 파일을 커밋 안 한 것”으로 해석
이推论은 충분히 그럴듯했다. 많은 저장소가 그렇듯 main 브랜치에만 집중하면 된다는 전제가 깔려 있었다.
원인 추적 — 전 브랜치를 봐야 하는 이유
사용자가 “CodeFill은 내 깃허브에 소스코드까지 다 있을 텐데”라며 재확인을 요구했다. 나는 GitHub API로 직접 전 브랜치를 훑기로 했다.
/repos/{owner}/{repo}/branches # 브랜치 목록
/repos/{owner}/{repo}/git/refs # 태그까지
결과는 처참했다:
| 항목 | 실제 | 내가 본 것 |
|---|---|---|
| 전체 브랜치 | 6개 (develop, feat/* 등) | 1개 (main만) |
| 실 소스 파일 | 200+ (src/, backend/, tests/) |
README.md 1개 |
| 커밋 총합 (중복 제거) | 340+ | 7 |
main에는 실제로 README.md만 있었다. 하지만 develop 브랜치에 전체 소스코드가 있었고, feat/ 브랜치들에 기능별 작업 이력이 쌓여 있었다. untracked로 보였던 디렉토리들은 전부 다른 브랜치 소속이었다.
“default 브랜치만 보면 안 된다. 반드시 refs 전체를 훑고, 브랜치별 커밋을 sha로 중복 제거해 합산해야 진짜 이력이 나온다.”
해결 — 진단 방법 교체
feedback_repo_diagnosis_all_branches.md에 다음 규칙을 문서화했다:
# 이전: branches/default만 조회
GET /repos/{owner}/{repo}
# 이후: refs 전체 + 브랜치별 커밋
branches = GET /repos/{owner}/{repo}/branches
for branch in branches:
commits += GET /repos/{owner}/{repo}/commits?sha={branch.name}
# sha 기준 중복 제거
- 검증: CodeFill 실측 결과 200+ 파일 · 340+ 커밋 · 6개 브랜치. “껍데기”에서 “실물 프로젝트”로 재분류.
- 남은 과제:
main만 보는 진단 카드 자동 갱신 로직이 아직 수동. 레포 추가 시 refs 전체 스캔을 강제하는 CI 훅을 검토 중.
SIEM 동의어 사전 — 5000라인 심사에서 bare→chat swap 규칙을 정립한 과정
proposal_automate 프로젝트는 한글 SIEM 제안서 검색을 위한 동의어 사전 후보를 5000라인 넘게 심사하는 작업이었다. 각 후보는 영문 키→한글 동의어 쌍으로, bare(동의어 미적용) 상태에서 ML 잡음(약어집·H/W스펙·로그량 등)을 얼마나 걸러내는지와 chat(동의어 적용) 상태에서 실장표 슬라이드를 얼마나 정확히 찾는지를 비교해야 했다.
증상 — 측정 데이터가 일관되지 않음
초기 측정에서 같은 후보인데도 실행할 때마다 결과가 달랐다. 동의어를 세련되게 다듬을수록 스코어가 바뀌었고, 그 바뀐 값을 기준으로 “채택”을 판단하면 판단 기준이 유동적이었다.
첫 가설과 붕괴
처음엔 “동의어를 더 정확히 다듬으면 성능이 올라간다”고 생각했다. 그래서 IoC → 침해지표 (침해지표/CTI) 같은 정제된 동의어를 넣어 재측정했다.
결과는 측정값이 완전히 달라졌다. IoC 후보가 bare에서 Random Forest ML noise로 분류되던 게, 정제된 동의어에서는 관련 슬라이드로 재분류되기 시작했다. 문제는 이게 “동의어가 좋아진 것”인지 “측정이 불안정해진 것”인지 구분할 수 없다는 점이었다.
“세련된 동의어로 바꾸면 측정 결과가 달라진다—그러면 그 달라진 결과를 기준으로 판단할 수 없다. 판단 기준이 유동적이면 증거 기반 심사가 아니다.”
원인 추적 — bare vs chat swap 규칙
방향을 바꿨다. 심사자가 직접 “이 동의어가 이겼다”고 판정한 정확한 동의어만 유지하고, 재측정으로 스코어를 바꾸지 않기로 했다. 즉, 동의어 자체는 손대지 않고 bare(동의어 미적용)의 top3와 chat(동의어 적용)의 top5를 나란히 놓아 swap이 일어났는지만 본다.
실제 비교 포맷:
DFIR | 포렌식 | bare top3: SFIRE-0098(glossary), NICE-0155(정규화), KYOBO-0134
-> chat top5: SFIRE-0098(glossary), NICE-0155(정규화), KYOBO-0134, KYOBO-0135(포렌식), ...
결과: bare가 glossary+정규화 잡음을 잡고, chat이 포렌식 슬라이드를 추가 → ACCEPT
해결 — 규칙과 측정 방법 정립
최종 규칙을 세 가지로 확정했다:
- bare가 약어집·H/W 스펙·로그량·ML 잡음을 잡고, chat이 실장표 슬라이드로 교체되면 ACCEPT
- 동의어는 심사자가 채택한 정확한 원형만 유지 — 세련된 변형 금지
- 측정은
에이전트/agent가 질의에 있을 때만 발동 — 수술적 재랭킹
검증은 agent_sense.py의 selfcheck + 10/10 스냅샷 일치로 했다. IoC 후보는 bare에서 Random Forest ML noise → chat에서 KB-0123·HL-0085·LOGPRESSO-0039(침해지표/CTI)로 정확히 swap되었다.
채택 현황:
| 카테고리 | 채택 | 기각 | 비고 |
|---|---|---|---|
| AD/계정인증 | 4 | 5 | SSO/SAML/LDAP 인증 슬라이드 정확 |
| DFIR/포렌식 | 3 | 2 | glossary+정규화 잡음 제거 |
| 단말/EDR | 2 | 1 | TTA 공인성능시험 슬라이드 제거 |
| 논리연산 | 1 | 3 | AND/OR/NOT 슬라이드만 유지 |
남은 과제: 하네스 출력이 압축돼 스니펫이 뭉개지는 현상이 남아 있다. 파일 출력으로 우회했지만, syn_goldgate.py에서 스니펫 길이 제한 로직을 별도로 분리할 필요가 있다.
오늘의 한줄
검증 없는 진단 카드는 오판을 낳고, 유동적인 심사 기준은 증거를 무력화한다 — 하드 데이터로 못박지 않으면 내 사고가 가장 큰 버그다.