무엇을 만들었고 어떻게 검증했는지 기록합니다. spec/이 "무엇을 만들 것인가"라면, 여기는 "어떻게 만들었나"와 검증 결과입니다.
이 폴더는 구현 이력을 기록합니다. 완료된 항목도 삭제하지 않고 상태 표시만 갱신합니다. 회고·복기에서 "무엇을 어떻게 만들었는가"를 추적하기 위함입니다.
- 완료 → 문서 유지 +
상태: 완료 - 무효화 → 문서 유지 +
상태: 무효(사유)표기 - 삭제 → 하지 않음. 잘못 작성된 문서도 정정으로 처리
※ spec/은 현재 유효한 명세이므로 이 원칙의 대상이 아닙니다(낡은 내용은 갱신·삭제).
| 번호 | 문서 | 유형 | 내용 |
|---|---|---|---|
| I9 | 2026-07-23-keyword-preset-seed.md | 구현 | Keyword Preset 27개 산출·검증 (ai#2) |
| I10 | 2026-07-23-architecture-diagrams.md | 구현 | architecture.md 구조도(Mermaid) 4종 (ai#4) |
| I14 | 2026-07-23-keyword-matching-eval.md | 검증 | Keyword 매칭 평가 A/B/C 요약·포인터 (판정 모델 gemini-2.5-flash 확정) |
| I19 | 2026-07-23-fastapi-implementation.md | 구현 | FastAPI scaffold + /context/process + /search 구현·검증 (ai#5·#6) |
| I20 | 2026-07-24-e3-test-harness.md | 구현 | E3 통합 테스트 하네스 + 저수준 27케이스 + 파이프라인 20 + Dockerfile + ai-ci 정비 (ai#14·#16·#18) |
| I21 | 2026-07-27-e2e-verification.md | 검증 | E2E 실경로 — 실제 AI API 프리셋 적재·파이프라인·검색 품질·하네스 동등성·권한 경계 |
| I22 | 2026-07-28-s1-implementation-recovery.md | 구현 | S1 세션 구현 판단 맥락 복원 — 설계선택 19·불변식·spec↔구현 불일치(구현결함)·인프라 미복원 |
| I23 | 2026-07-29-dev-deployment-gates.md | 구현 | dev 배포 게이트 3종 — /ready·GMS_BASE_URL fail-fast·AI API 양방향 스모크 (ai#33) |
| I25 | 2026-07-29-demo-seeding.md | 구현 | 데모 시딩 — back API 경로 시딩(tools/demo_seed/)·AI API 건수 판단·E2E 재확인 (Jira 작업) |
| I41 | 2026-07-30-retry-and-error-classification.md | 구현 | 외부 API 재시도·오류 분류 정합화 — 429/LLM 4xx 오분류 정정·짧은 재시도·오류 경로 테스트 (Jira 작업) |
| I26 | 2026-07-30-coverage-gate.md | 구현 | app coverage 게이트 활성화 — line·branch 각각 80% 차단·부트스트랩/기동 계층 신설·§4.2 계층 구분 명문화 (Jira 작업) |
| I24 | 2026-07-29-sealed-secret-handoff.md | 구현 | Runtime Secret handoff — Environment 경계 계약·공급망 pin (Jira 작업). 상태: 대체 — 봉인 실행은 Infra 공용 action으로 이관, Jira 작업 판 설계 근거는 같은 문서에 보존 |
| I43 | 2026-07-30-real-data-e2e.md | 검증 | 실사용자 데이터 E2E — 검색 10/12·피드·Keyword PASS, 시딩 15분 8초·37건, AI API 32,912 토큰 (Jira 작업) |
| I44 | 2026-07-30-judge-vendor-fallback.md | 구현 | 판정 LLM 벤더 폴백 — 429가 프로바이더 경로별로 걸린다는 실측·어댑터 3종·시도 예산 공유·응답 벤더 기록 (Jira 작업) |
| 없음 | 2026-07-31-ticket-audit-96-77.md | 감사 | 티켓 대조 — -96 완료 조건 5개·-77 정정 요청 8개를 ai·infra·docs 실물과 대조해 해소/미해소/무효 판정. 둘 다 닫을 수 있음 (Jira 작업·-77). 번호 없음 — 이 문서 자신이 "구현 기록이 아니라 대조·판정 기록이다. 만든 것이 아니라 확인한 결과"라고 명시한다(전수 표는 산출을 세는 곳이다) |
| I27 | 2026-07-31-embedding-grid.md | 검증 | 임베딩 4조건 실경로 측정 — 입력 구성 × 모델 교차. 1위 일치 A 10 · B 9 · C 10 · D 10 / 12, top-3 넷 다 12/12. -174 의 8번 진단이 틀렸음을 B·C 대비가 보인다 (Jira 작업) |
| I28 | 2026-07-31-gms-call-observability.md | 구현 | AI API 호출·재선점 로그 계측 — 호출 1회당 벤더·모델·상태·결과 분류·지연, 60초 창 실패율, 만료 PROCESSING 재선점. httpx 가 요청 URL 을 INFO 로 흘리던 것을 함께 차단 (Jira 작업) |
| I39 | 2026-07-31-seed-guard.md | 구현 | 시연 도구 결함 3건 — 쓰기 컬럼 계약·고아 집계·JWT 키 실검증을 --reset 앞에 두는 preflight. 셋 다 어긋내 RED 확인 (Jira 작업) |
| I30 | 2026-07-31-candidate-threshold.md | 검증 | 후보 유사도 임계값 τ 재검증 — 현행 0.30 유지. fit min 0.3001 과 unfit max 0.4225 가 겹쳐 τ 로는 「붙여도 되는가」를 가를 수 없다 |
| I29 | 2026-07-31-search-cut.md | 검증 | 검색 결과 컷 τ_abs=0.30 · r=0.60 — 정답 누락 0/12 · 빈 결과 0/12 · 꼬리 76.3% 제거 · 무관 질의 11/15 침묵. 무관 질의를 1건에서 15건으로 늘리자 §6 의 「간격 +0.2120」이 -0.0176 으로 뒤집혀 컷 미적용 판단을 개정 (Jira 작업) |
| I40 | 2026-07-31-judge-prompt-rule.md | 검증 | 판정 프롬프트 「본문에 근거 없으면 미선택」 개정안 둘 — 둘 다 채택하지 않는다. 오분류 감소가 같은 프롬프트를 다시 돌렸을 때의 변동을 넘지 못했고, 사용자가 보는 손실(fit 0건 Context 8.00)은 세 조건이 같다 (Jira 작업) |
| I31 | 2026-07-31-search-error-contract.md | 구현 | 검색 API 오류 응답 계약 — TransientError→503 · PermanentError→502, 500 을 「우리 코드의 결함」으로 비워 둔다. 운영 버그 ai#69(임베딩 502 → 검색 500). back 은 500·503 을 구분하지 않으므로 바뀌는 것은 사용자 화면이 아니라 관측 (Jira 작업) |
| I34 | 2026-07-31-gms-error-body-redaction.md | 구현 | 게이트웨이 오류 본문 마스킹 — 응답 본문 200자가 예외 메시지를 타고 다섯 곳의 로그와 트레이스백으로 나가던 경로를 원천에서 막는다. 실제 AI API 로 네 경로에 오류 19건을 넣어 실측: 자격 증명은 한 건도 에코되지 않고, endpoint 는 맨 호스트로 실리며, OpenAI 는 요청 값을 앞뒤 3자만 남기고 잘라 되돌린다 (Jira 작업) |
| I32 | 2026-07-31-db-error-classification.md | 구현 | DB 실패의 오류 분류 — -220 이 남긴 500 을 메운다. SQLSTATE 군 단위 경계, 미분류(500) 목록이 분류 목록만큼 중요. -220 의 핸들러를 고치지 않고 하위 타입으로 받는다 (Jira 작업) |
| I33 | 2026-07-31-judge-vote.md | 구현 | 판정 n회 다수결 PINLOG_JUDGE_VOTE_N — 비결정성 24%→12% · 흔들리던 오분류 13종 완전 제거 · 정상 판정 손실 0. 그런데 오분류 행은 거의 안 준다(10.13→9.17, p=0.210) — 다수결은 소수의견을 지우는 대신 다수의견을 굳힌다. n=1 유지 (Jira 작업) |
| I35 | 2026-07-31-docs-index-check.md | 구현 | 문서 색인 정합을 ai-ci / check 로 옮긴다 — 번호 중복 · 고아/누락 둘만. 07-31 사고 1·2·3 을 실제 docs/ 사본으로 재현해 잡는 것과, 결번·두 표 불일치를 통과시키는 것을 함께 고정. 착수 시점 dev 위반 3건 정리. 표 이중화는 줄일 수 없다(두 표의 집합이 다르다) (티켓 없음) |
| I36 | 2026-08-03-gms-vision-probe.md | 검증 | AI API 게이트웨이 멀티모달(이미지) 지원 재고 — 지원한다. 1×1 PNG 를 세 벤더 스펙(OpenAI·Gemini·Anthropic) 그대로 실어 각 1회씩 총 3호출, 셋 다 200 이고 이미지 내용에 실제로 답함. back#138 이 이미지 분석 흐름의 유일한 선행 조건으로 남긴 것을 해소 (Jira 작업) |
| I42 | 2026-08-03-docs-index-oneway.md | 구현 | 구현 리포트 파일 표에 번호 컬럼을 넣어 반대 방향 누락(⑤)을 잡는다 — -226 이 한 방향만 켜고 남긴 구멍. 번호 없던 7건 판정(5건 신규 번호·1건 기존 I30 링크 보완·1건 「없음」 명시), 병합 중 실제로 난 번호 충돌(T56)도 재현·해소 (Jira 작업) |
| I37 | 2026-08-03-dead-config-keys.md | 구현 | 죽은 설정 키 전수조사 — PINLOG_JUDGE_MODEL(-175 가 대체)·PRESET_CACHE_TTL_SEC(ai#24 가 이미 제거) 둘 다 저장소는 이미 정리돼 있었고 개발자 로컬 .env 잔재만 남아 있었음을 런타임 sentinel 주입으로 확인(grep 만으로 끝내지 않음). 배포 Secret(8키) 은 애초에 둘 다 담지 않음. .env 13 vs .env.example 12 는 단일 차집합이 아니라 양방향(2:1)이었고 둘 다 정상이라 example 변경 불필요. -197 계측이 벤더·모델을 이미 로그로 남김을 확인 — 보강 불필요 (Jira 작업) |
| I45 | 2026-08-03-search-recall-probe.md | 검증 | 「본문에 있는 말로 검색해도 안 나온다」 원인 판별 — 셋이 같은 원인이 아니다. 그네는 컷(풀면 3위·①), 신한·부캠은 컷을 풀어도 6위·8위(③). 가른 것은 무관 기준선이다 — 어느 본문에도 없는 치과의 top-1(0.2953)이 본문에 그대로 있는 스팟(0.2438)보다 높다. #87의 BPE 가설은 토크나이저 실측으로 반증(그네↔그네팟 접두 토큰 공유) (Jira 작업) |
| I38 | 2026-08-03-error-wording-split.md | 구현 | 오류 응답 문구 분리 — -221 이 상태 코드·로그는 맞혔지만 본문은 embedding upstream ... 그대로였다. DatabaseTransientError/DatabasePermanentError 를 보고 database unavailable/database rejected the request 로 가르되 원인 값은 여전히 안 싣는다. static/05 는 문구를 소유하지 않고 back 의 AiSearchClient.translate 가 본문을 안 읽어 반영하지 않음 (Jira 작업) |
| I47 | 2026-08-03-word-query-cut.md | 구현 | 단어형 질의로 컷 격자 재측정 — τ_abs 를 질의 길이로 가른다(단어형 0.24 / 문장형 0.30, r 은 갈리지 않는다). 두 대역이 겹치지 않아(문장형 정답 하한 0.3642 · 단어형 0.2438) 단일값으로는 한쪽이 반드시 손해를 본다. 계약이 지목한 「문장형 회귀」는 격자 240조합 전량에서 0 이었고(컷을 푸는 방향이라 구조적) 진짜 손실은 문장형 무관 질의 침묵이 11/15→5/15 로 무너지는 것이었다. 채택값을 정한 것은 회복률이 아니라 「1위 손실」 — 0.26 은 회복 96% 인데 컷 전 1위인 정답 3건을 0건으로 만든다(비건→플랜트가 1위인데 0건). 0.24 는 「1위 정답을 하나도 안 잃는 가장 높은 값」(0.25 부터 깨진다). ai#87 실서버 회복 확인 · 실서버 대조 87/87. T68 대응으로 재현성 회차 3개를 함께 냈다 — 마진 0.0038 이 T68 실측 잡음 0.0044 보다 작아 값 자체가 위협받았으나, 이 조건의 흔들림 상한은 0.000209(21배 작다)이고 경계점 스팟 은 스프레드 0.000000, 격자 판정은 τ=0.20~0.30 전 구간 3회 일치. word_sweep.py --repro 를 상시 관측으로 남겼다 (Jira 작업) |
| I46 | 2026-08-03-preset-description.md | 검증 | 프리셋 description·examples 개정 3조건 측정 — examples 만 채택(E), description 개정은 기각. 네 티켓 만에 처음 채택한다. 두 필드가 들어가는 곳이 달라(examples 는 임베딩만, description 은 임베딩+판정 프롬프트) 갈라 쟀고, 갈라 재지 않았으면 D 의 해로움이 DE 안에 묻혔다. E 오분류 11.00→6.90행(p=0.0001)·정상 손실 -0.80(p=0.16)·교환비 0.20·순이득 부호가 양극단 모두 양수(네 티켓 중 유일). D 는 fit 0건 Context 8.20→10.00 으로 악화(범위 분리)하고 안 고친 22종이 정상 -2.50 를 치른다. 자기충족 방어 셋(라벨 무관 수정 원칙·안 고친 22종 대조·라벨 밖 홀드아웃 30건). T68(임베딩 비결정성)·T69(τ 격자가 rank 로 밀린 행을 안 셈) 발견·우회 (Jira 작업) |
| I48 | 2026-08-03-dev-deploy-gap.md | 감사 | dev 병합이 배포에 닿지 않는 구간 — ai-image / publish SKIPPED 는 고장이 아니라 설계다. ai-ci.yml 안의 image-publish job 이고 push × refs/heads/main 두 조건이 모두 필요하다(별도 워크플로가 아니라 워크플로 목록에 안 보인다). 근거가 주석·계약 테스트·infra 어서션·CONTRIBUTING 넷이라 한 곳의 실수일 수 없다. apps/dev/ai 가 pin 하는 것은 ai 의 main HEAD 이고 ai 의 dev 브랜치는 배포 경로에 없다 — 끊긴 것은 파이프라인이 아니라 릴리스 PR 이다. ai-image-update 의 success 는 「갱신했다」가 아니라 「확인했고 갱신할 것이 없었다」(자동화는 08-03 00:37Z 에 되살아나 infra#172 로 한 번 동작했다). 새 봉인 값 preset-ab321360b0df 산출 — 방식은 파일 전체 SHA-256 앞 12자이며 문서에 적힌 64자와 전량 대조해 확정했다. bootstrap.version 은 어떤 자동화도 갱신하지 않고 틀려도 아무 신호가 없다 (티켓 없음) |
| I49 | 2026-08-03-gms-image-probe.md | 검증 | AI API 이미지 실사용 조건 두 축 — 축 A 생성은 지원한다(gpt-image-1·gemini-2.5-flash-image 둘 다 1024×1024 PNG, 게이트웨이에 모델 allowlist 가 있어 dall-e-3·imagen-3 은 차단). 축 B 는 가설 C — 토큰은 치수에 붙고 바이트에는 안 붙는다(512×512 고정에서 바이트 36배에 prompt_tokens 8,523 불변). -227 의 8,524 는 게이트웨이 가산이 아니라 gpt-4o-mini 의 타일 요금이다(텍스트 23 + 2,833 + 5,667×타일, 7개 조건 잔차 0). 그리고 더 급한 것이 나왔다 — 게이트웨이 요청 본문 상한이 96,024 B 통과 · 129,564 B 거부 사이에 있고, 넘으면 「Model not found in request」로 온다(-205 T62·-225 와 같은 원인). 실사용 대화 캡처(200 KB~2 MB)는 토큰을 쓰기 전에 이 벽에 먼저 막힌다 — ai#98 의 전제가 바뀐다. 정책 거절은 벤더마다 층이 달라 OpenAI 는 400, Gemini 는 200 + finishReason=PROHIBITED_CONTENT 다 (Jira 작업) |
| I52 | 2026-08-05-search-rank-baseline.md | 구현 | 검색 순위 지표 baseline tools/search_cut/rank_score.py — 기존 지표가 전부 컷 기준이라 순위 변화를 못 센다(P48 1단계 이후는 순위를 바꾸는 변경이라 개선이 0으로 보인다). 기존 행렬만 읽어 Hit·Recall·MRR·nDCG@1/3/5 를 컷 전/후 · 단어형/문장형 · 정답질의/무관질의로 갈라 낸다. 재구성 일치 두 건 — 무관-문장형 침묵률 11/15 와 스팟 0.2438 이 기존 기록과 같다. -255 ① 판정이 이미 닫혀 있음을 확인(-266 의 단어형 분기가 그네 를 회복시켰다). 신한·부캠 은 tau_word 와 r·top1 양쪽 모두 아래라 컷 조절로 닿지 않는다 (티켓 없음 · 런타임 변경 없음) |
| I50 | 2026-08-03-preset-display-name-deploy-chain.md | 검증 | 표시명 개정의 배포 체인 여섯 단계와 소관 — 봉인 값은 판 표기이지 부트스트랩 재실행 조건이 아니다. 낡은 봉인 값 위에서 새 표시명이 운영 화면까지 온 것이 곧 재실행의 증거다(hook-delete-policy 가 같은 이름 Job 을 지우고 다시 만들고, 적재는 멱등이다). 중앙이 「값이 같으면 재실행 안 된다」로 판정했고 독립 검증이 논거를 반증했으나 클러스터 관측 경로가 없어 「확인 불가」로 끝났던 것을 배포가 닫았다. 릴리스가 두 번 막힌다 — base 검사(main base 는 release/*·hotfix/* 만)와 strict 상태 검사(BEHIND 거부). 화면 확인이 유일한 검증이라 이르게 보면 「안 됐다」로 오판한다 (Jira 작업) |
| I51 | 2026-08-05-short-query-boundary.md | 검증 | 짧은 질의의 층별 탈락과 단어형 경계 두 정의 — -266 이후 남은 실패는 셋뿐이고 경계 정의로는 하나도 안 풀린다. 신한(③ τ_abs·6위)·부캠(③ τ_abs·8위)·신한 부캠(④ r·4위)이고 실서버 대조 22/22 일치로 층을 확정했다(전처리·폴백은 존재하지 않는다 — min_length=1 뿐이고 0건이면 0건이 그대로 나간다). 「2자가 안 걸린다」는 부정확 — 2자 7건 중 5건은 걸리고 못 걸리는 둘은 컷 전 순위가 6·8위다. -266 이 「재지 않았다」고 적은 B(공백·짧다)·C(무공백·길다) 대역을 본문 인접 어절쌍 전량(쌍 176종 × spaced/joined × 소유자 3명 = 1,128행)으로 처음 쟀다 — 손실은 C 에 몰려 있고(16/122 → 어절 수 정의 3/122) 그런데 초점 집합 36행에서는 어느 정의에서도 누락이 0 이라 두 표가 어긋난다(회복되는 것이 기능어 쌍이다). 새 관측 둘: 공백을 떼면 유사도가 내려간다(198짝 평균 -0.0385 · 172/198 하락)는 것과 전각 공백은 더 내려간다(신한 부캠 0.3187→0.2753 으로 τ 탈락 — -266 이 「안전 방향」이라 부른 판단이 이 대역에서는 손해다). 계약이 「이것이 첫 측정」이라 못박았으나 -255·-266 이 선행하므로 세 번째다 (Jira 작업) |
| I53 | 2026-08-05-fusion-measurement.md | 구현 | Keyword fusion 1단계 실측 — 병합 방식(가중합 5조합·RRF cutoff 5값)×floor 4값 격자. 가중합은 정답과 관련 없는 후보를 함께 끌어올린다 — floor 0.25 에서 신한 복구(컷 후 4위)와 무관-문장형 무노출 11/15→9/15 감소가 같은 계산에서 나온다. RRF 는 무노출을 유지하나 문장형 hit@1 0.8333→0.5833 퇴행. P48 §6.1 네 조건을 전부 통과한 조합은 floor 0.35 · binary w=0.05 하나 — 그 조건에서 단어형·문장형 전 지표 개선·무관 무노출 유지, 대가는 신호 반영 12/66 축소와 신한 재제외. 신한·부캠은 이 구조로 안 풀린다(재작성 -337·문자열 검색의 대상). 환경 동등성은 baseline 재현 = I52 일치로 확인. 못 잰 축(floor 0.30~0.35·weight 격자·재현성 회차)은 -339 몫 (Jira 작업) |
| I54 | 2026-08-06-lexical-merge-rule.md | 구현 | 문자열 검색 병합 규칙 실측 — 게이트 3단(전체/단어형 한정/어절 경계) × 병합 3종(뒤 추가/RRF/앞 고정) 격자. 무관·타인소유·문장형 세그먼트에서 문자열 매치 0건 — 어떤 조합도 관련 없는 결과 노출을 늘리지 않는다. 신한 이 전 조합에서 회복(RRF 5위)되고 부캠 은 예상대로 미회복(재작성 -337 대상). 경계 게이트는 기대 정답 6건을 제외하는 손해만 관측돼 부분일치 채택 권고. 규칙 확정안: 단어형 한정·부분일치 게이트 + RRF(k=60) 병합 + 컷·응답 계약 불변 + 실패 시 벡터만 — back 연동 티켓의 판단 재료. 한계: 경계 게이트가 막으려는 유형(어절 중간 시작·부수적 언급·동형어)이 코퍼스에 없어 이득 측정 불가, 운영 코퍼스 재평가 필요 (티켓 없음 · 런타임 변경 없음 · 스냅샷 DB) |
| I55 | 2026-08-06-rewrite-effect.md | 구현 | LLM 질의 재작성 효과 실측·길이 게이트 확정 — 실제 RewriteClient(운영 체인)로 무관 15건·사례 5건 전/후 비교. 약어 회복 확인(부캠→부트캠프 컷 전 8위→1위·신한 부캠 4위→3위, 둘 다 반환 회복)·고유명사 유지 규칙 작동(신한·그네·스팟 원문 불변). 전면 적용은 무관 무노출 11/15→9/15 로 채택 기준 미달 — 의미 불변 표현 정규화(파는 데→파는 곳)가 컷 경계 아래 대역을 밀어 올린다. 게이트 후보 8종 전수 시뮬레이션에서 형태(길이) 기준만 충족 — 성과 기반(결과 0건·top-1 임계)은 두 집단 대역이 겹쳐 분리 불가에 회복 대상까지 놓치고(원문도 오답 3건 반환), 단어형 판정 재사용은 신한 부캠 상실. 게이트 확정: strip 후 6자 이하만 재작성(SEARCH_REWRITE_MAX_CHARS, 사용자 확정) — 게이트 반영 재측정에서 무노출 11/15 유지·회복 2건 유지로 채택 조건 충족. 한계: 6~12자 대역 무관측·모델 1종 1회차 관측 (Jira 작업 · 스냅샷 DB) |
| I56 | 2026-08-06-rerank-adoption.md | 구현 | 키워드 재정렬 채택값 실측·런타임 구현 — P49 §4 재정렬 전용 구조(컷 통과 집합 고정·순서만 조정)에서 BASE·binary(floor 5값×weight 4값)·RRF(k=60) 재측정. binary·floor 0.35·w 0.05 만 퇴행 없이 개선(단어형 hit@1 0.8636→0.8788 · hit@3 0.9394→0.9545 · MRR 0.9015→0.9129, 문장형 유지+MRR 개선), RRF 는 문장형 hit@1 0.8333→0.5833keyword_status=COMPLETED 신호·실패 시 벡터 순서 복귀·similarity 원값 유지, on/off 후보 집합 불변 계약 테스트 9건 + 픽스처 8건(총 511 통과). 실서버 on/off 대조 5판정 93건 전부 통과 — 재정렬 발화 7건 전건 오프라인 일치(만두 4위→1위), 신한·부캠 미회복 재확인(역할 분담 검증), 흔들림 분류 미발동. 번호 잠정 I56 — 병렬 잠정 I55 충돌을 통합 병합에서 색인 검사가 잡아 나중에 병합된 이쪽이 I56 으로 재확정(설계된 절차), 최종 확정은 dev 병합 직후 (Jira 작업 · 스냅샷 DB) |
| I57 | 2026-08-07-preset-label-realign-gate.md | 검증 | 프리셋 표시명 정합 회복과 게이트 재검증 — 표시명 명사형 통일(-292)이 YAML 정본에만 반영되고 두 DB에는 옛 표시명이 남아 프리셋 임베딩·측정 자산이 정본과 어긋나 있던 결함을 스냅샷 재적재로 해소. 조작 범위를 md5 4종으로 대조해 Context 임베딩·판정 상태·context_keyword 83행 불변 확인(판정 불변). 새 임베딩 격자에서 채택값(binary·floor 0.35·w 0.05·top_k 3) 유지 — 퇴행 없이 단어형 개선(1위 0.8636→0.8788·3위내 0.9394→0.9545·MRR 0.9015→0.9121). 변화: 문장형 개선(MRR 0.9028→0.9167)이 사라지고 floor 0.40 구간으로 이동 — 재정렬의 이득이 단어형에 집중. 경계 감도 0.345~0.36 안정하나 기준선 0.35 인접 쌍 10건 중 9건이 임베딩 흔들림 폭 이내라는 한계 기록. 결정성 2회 일치. 게이트 5기준 전량 재통과(3 phase 재실행 — judge가 세 결과를 함께 읽어 부분 재실행 불성립). keyword_matrix 포트 가드를 시연 DB에서 스냅샷으로 정정 동반 (검색 고도화 트랙 공통 · 스냅샷 DB) |
| I58 | 2026-08-07-gate-threshold-remeasure.md | 검증 | 결합 신뢰도 게이트(중앙 조정 세션 OFFTOPIC-CONFIDENCE-GATE-HANDOFF-DRAFT.md §4) threshold 후보 0.35 오프라인 재측정 — SEARCH_KEYWORD_RERANK_FLOOR에서 값만 가져온 것이 아니라 이 용도(S1 단독·저유사도 결과 숨김)로 별도로 다시 쟀다. gate_sweep.py가 기존 행렬(문장형 정답 12·단어형 정답 66·진단프로브 22·무관 60·타인소유 96건)에 게이트 규칙을 적용해, threshold 0.35에서 정답 손실 0을 유지하며 무관 노출이 크게 준다(단어무관 23/45·타인소유 55/96 신규 침묵)는 것을 확인했다. 최초 실행은 recall_probe.json의 최상위 user_id 처리 버그로 프로브 손실 6건을 오탐했고, 고친 뒤 0.35 채택을 권고했다 (Jira 작업 · 스냅샷 없음 · DB·AI API 호출 0회) |
| I59 | 2026-08-07-search-keyword-matched-field.md | 구현 | 검색 응답에 keywordMatched: bool 필드 추가 — _rerank_by_keyword가 이미 계산하던 Preset 매치 여부를 정렬에만 쓰고 버리던 것을 (list, matched_ids) 튜플로 바꿔 응답까지 싣는다. similarity는 원래 코사인 값 그대로 유지(정렬 점수 비노출 계약 불변), 재정렬이 생략되는 모든 경로는 빈 집합이라 자연히 False. 결합 신뢰도 게이트가 쓸 S3 신호 준비 (Jira 작업) |
| I60 | 2026-08-07-rerank-gate-contract-boundary.md | 구현 | 재정렬 후보 불변 계약(②)과 결합 신뢰도 게이트(back 레포 Jira 작업)의 계약을 분리 명시 — test_search_rerank.py docstring에 "이 파일이 고정하지 않는 것" 절을 추가하고, 게이트라면 제외했을 모양의 후보도 재정렬은 지우지 않는다는 것을 test_rerank_never_gates_a_weak_unsupported_candidate로 실행 고정 (Jira 작업) |
유형: 구현(무엇을 만들었나) / 검증(어떻게 검증했나) / 감사(티켓·문서가 실물과 맞는가). 검증 성격 문서가 늘면 이 컬럼이 분류 기준이 된다. 분리 트리거: 리포트가 15개를 넘고 검증 유형이 절반 이상이면
verification/분리를 검토한다.
| I | 산출 | 반영처 |
|---|---|---|
| I1 | AI 공용 설계 단일 원본 static/05_AI_설계.md(836줄, 21 테스트 시나리오) |
docs#2 |
| I2 | static/05-1 파트간 요구사항(front/infra) |
docs#3 |
| I3 | API 상세명세 draft/11(디자인 화면→엔드포인트) |
docs#4·#5 |
| I4 | AI 구현 명세 10문서 | spec/ |
| I5 | version-race-control → deletion-race-control 리네임·재작성 |
spec/deletion-race-control.md |
| I9 | Keyword Preset seed 27개 | preset-seed 리포트 |
| I10 | architecture 구조도 4종 | 구조도 리포트 |
| I11 | 세 PR 초안(docs/ai/back 제목·본문·리뷰포인트) | docs#2·ai#1·back#1 |
| I12 | MINYONG 공유 코멘트(결정 4건) | docs#2 |
| I13 | eval 하네스 A/B/C (tools/keyword_eval/) |
test/keyword-matching-eval |
| I14 | eval REPORT(A/B/C-1) — 보정 불필요·프롬프트 확정·하한 0.30 | eval 리포트 |
| I15 | /search 응답 contextId 추가(DISTINCT ON, Spring matchedContext 조립) — 소규모 변경이라 전용 리포트 없이 인벤토리만 |
ai#11·docs#10, P40, spec/personal-search.md |
| I16 | AI 작업기록 문서(구조도+ADR 4+트러블슈팅+리포트 5) | 이 트리 |
| I17 | 문서화 규약 메모리 | (로컬 메모리) |
| I18 | 누적 계획 파일 | (로컬 plans) |
| I19 | FastAPI 구현(scaffold + /context/process + /search + Preset 부트스트랩) |
FastAPI 리포트 |
| I20 | E3 통합 테스트 하네스 + 저수준 27 + 파이프라인 20 + Dockerfile + ai-ci 정비(Python 3.12·lock·Jira 검증) | E3 리포트, PR ai#14·#16·#18 |
| I21 | E2E 실경로 검증 + 검증 드라이버(tools/e2e/) — 문서 마찰 F1·F2·F5·F6, 하네스 동등성 실측, 권한 경계 실증(-61 근거), 시딩 가능 범위 |
E2E 리포트, tools/e2e/ |
| I22 | S1 구현 판단 맥락 복원 — 설계선택 19·불변식·spec↔구현 불일치(구현결함 A·F절)·실행 인프라 미복원 | S1 복원 리포트 |
| I23 | dev 배포 게이트 3종 — GET /ready(DB+Preset, AI API 미호출) · GMS_BASE_URL /gmsapi/ fail-fast · app.smoke.gms_roundtrip(embedding+judge 실호출, 한쪽 실패 시 exit 1) |
배포 게이트 리포트, ai#33 ← ai#32 요청 |
| I24 | Runtime Secret handoff — 평문 없이 Infra 에 전달. Jira 작업 판은 Actions Secret 7종을 kubeseal --raw 로 직접 봉인(pinlog-dev/ai-owner-secrets, scope strict), Jira 작업에서 Environment 경계 + Infra 공용 action 으로 이관하고 앱 Secret 3종으로 축소. 설계 근거는 문서에 보존 |
handoff 리포트 ← ai#32 요청 ① |
| I26 | app coverage 게이트 tools/check_coverage_gate.py — line·branch 를 따로 판정(합산 비율은 statement 수에 가려 branch 미달을 통과시킨다), 임계값은 스크립트 상수라 CI 인자로 덮을 수 없음. 부트스트랩·기동 계층 테스트 신설(둘 다 기준선 0%·58%), 146→181 tests, line 88.80→99.74% · branch 82.08→98.11%. RED 4종 실측 |
게이트 리포트, tools/check_coverage_gate.py |
| I25 | 데모 시딩 도구 tools/demo_seed/ — back API 경로로 member 5·Context 14·Collection 9 생성, --reset 재현, verify.py 시연 3종 판정. AI API 429(분당 약 2건) 실측과 그에 맞춘 회수 루프 |
데모 시딩 리포트, tools/demo_seed/ |
| I27 | 임베딩 4조건 측정 하네스 tools/emb_grid/ — 조건 정본 하나(conditions.py)를 셸에 eval 로 넘겨 값이 갈라지지 않게 하고, 조건이 환경과 어긋나면 재기 전에 멈춘다(profile·차원·프리셋 적재 여부·FastAPI 도달). alter_dim.py 는 로컬 DB 차원을 바꾸고 되돌린다 — Flyway 를 만들지 않는 것이 이 측정의 계약이다 |
4조건 측정, tools/emb_grid/ |
| I28 | AI API 호출·재선점 로그 계측 — 호출 1회당 벤더·모델·상태·결과 분류·지연, 60초 창 실패율 집계, 만료 PROCESSING 재선점. _usage.py(토큰)와 합치지 않은 근거와 try_start CTE 재작성. httpx 가 요청 URL 을 INFO 로 흘리던 것을 함께 차단 |
AI API 호출 관측, failure-recovery §2.4 |
| I29 | 검색 결과 컷 하네스 tools/search_cut/ — 질의를 한 번 임베딩해 굳히고 τ_abs × r 격자를 오프라인으로 훑는다(AI API 배치 1회). 정답이 없는 무관 질의 15건을 별도 축으로 둔다 — 검증 질의만 재면 r 이 무관 질의를 침묵시키지 못한다는 사실이 보이지 않는다. verify_live.py 가 실서버와 27/27 정확 일치를 확인(-210 과 달리 근사가 아니라 대조군 불필요) |
컷 측정, tools/search_cut/ |
| I52 | 검색 순위 지표 하네스 tools/search_cut/rank_score.py — 기존 행렬 셋만 읽어(DB 0회·AI API 0회) Hit@k·Recall@k·MRR·nDCG@k 를 낸다. 세 가지를 가른다 — 컷 전/후(컷 후만 보면 순위 개선이 가려진다), 단어형/문장형(대역이 겹치지 않아 합산 평균이 어느 쪽도 설명하지 못한다), 정답질의/무관질의(좋은 방향이 반대라 섞으면 상쇄된다). MRR 만으로 판정하지 않는다 — 단어형 정답이 본문 문자열 포함으로 계산돼 복수라, 정답 3개가 1·2·3위 든 1·9·14위 든 MRR 이 같다. 재구성이 맞다는 증거 두 건이 독립적으로 나왔다(무관-문장형 침묵 11/15 = -213 기록 · 스팟 0.2438 = config.py 주석의 마진 0.0038). 비상 스위치를 단어형 분기보다 앞에 두는 순서까지 원본대로 옮겼다. 가드 4종은 수치를 내지 않고 실패한다(profile 불일치·필드 누락·정답질의에 정답 0건·무관질의에 정답 존재). 결정성은 두 번 실행 바이트 일치로, 입력 추적은 SHA-256 기록으로 확인. 판정 하나가 바뀌었다 — -255 의 ①(그네 는 컷이 잘랐다)은 -266 의 단어형 분기로 이미 해소됐고, 신한·부캠 은 tau_word·r·top1 양쪽 모두 아래라 한쪽만 풀어도 회복되지 않는다(r 은 top1 이 높을수록 세게 자르므로 무관한 1위가 강할수록 정답이 확실히 잘린다) |
순위 baseline, P48, tools/search_cut/rank_score.py |
| I30 | 후보 임계값 τ 측정 하네스 tools/tau_grid/ — 42×27 유사도 행렬을 한 번 떠서 임의의 τ 를 AI API 호출 없이 재구성한다. 라벨 83행(labels.yaml)이 채점 기준이고 unclear 를 따로 둬 낙관·비관 양 끝을 함께 낸다 |
τ 재검증 리포트, tools/tau_grid/ |
| I41 | 외부 API 재시도·오류 분류 정합화 app/core/errors.py(classify_http_status 단일화)·app/client/retry.py(RetryPolicy) — 두 클라이언트가 상태 코드를 정반대로 분류하던 구현 결함(429→Transient, LLM 400/401/403→Permanent)을 고쳤다. SchemaViolationError 로 구조화 출력 위반을 하위 타입 처리, PermanentError → _fail() 결선 추가 (Jira 작업) |
재시도·오류 분류 리포트 |
| I43 | 실사용자 데이터 E2E 검증 — 가공 14건 + 실사용자 기록 23건 = 37건 통합 경로. 검색 정확도 10/12(83.3%) · 탐색 피드 PASS · Keyword PASS, 시딩 42초 · 토큰 32,912(판정이 임베딩의 17배) (Jira 작업) | 실데이터 E2E 리포트 |
| I44 | 판정 LLM 벤더 폴백 app/client/vendors.py(신설)·PINLOG_JUDGE_CHAIN — 429가 프로바이더 경로별로 걸린다는 실측 위에 벤더 어댑터 3종(OpenAI·Gemini·Anthropic)을 두고 시도 예산을 공유, 실제로 답한 벤더를 model_profile에 기록 (Jira 작업) |
벤더 폴백 리포트 |
| I39 | 시연 도구 결함 3건 방어 tools/demo_seed/preflight.py(신규) — 쓰기 컬럼 계약 대조·미적용 마이그레이션 차단·JWT 키 실검증을 --reset 앞에 두는 preflight. 셋 다 일부러 어긋내 RED 확인 (Jira 작업) |
시연 도구 결함 리포트, tools/demo_seed/preflight.py |
| I40 | 판정 프롬프트 「본문에 근거 없으면 미선택」 개정안 둘 — 둘 다 채택하지 않는다. 오분류 감소가 같은 프롬프트를 다시 돌렸을 때의 변동을 넘지 못했고, 사용자가 보는 손실(fit 0건 Context)은 세 조건이 같다 (Jira 작업) |
판정 프롬프트 리포트 |
| I31 | 검색 API 오류 응답 계약 — app/main.py 예외 핸들러 2종과 그 계약 테스트 tests/test_api_error_contract.py(MockTransport→실제 client→router→응답을 한 요청으로 관통). 분류·재시도는 -121 이 이미 맞혔고 비어 있던 것은 예외가 응답이 되는 지점이었다. 로컬 스텁으로 502 를 만들어 origin/dev 500 ↔ 이 브랜치 503 을 대조 |
오류 응답 계약, failure-recovery §2.5, ai#69 |
| I32 | DB 실패의 오류 분류 app/core/db_errors.py — -220 이 남긴 항목. 접속 실패는 asyncpg 예외가 아니라 stdlib OSError 라서 티켓 문구대로 asyncpg 만 분류했으면 본체를 놓쳤다(T53). 경계는 "서버·연결의 상태 때문인가, 우리가 보낸 질의 때문인가" 한 줄이고 미분류(500) 목록이 분류 목록만큼 중요하다. 분류를 세션 경계(db.py)에 걸어 획득·질의를 함께 덮고, 하위 타입(DatabaseTransientError)으로 -220 의 핸들러를 그대로 재사용. 전용 컨테이너를 docker stop 해 origin/dev 500 ↔ 이 브랜치 503 을 대조 |
DB 오류 분류, failure-recovery §2.5, T53~T55 |
| I33 | 판정 n회 다수결 PINLOG_JUDGE_VOTE_N + 하네스 tools/judge_vote/ — 엄격 다수결(votes*2 > n), 분모는 성공 수가 아니라 n(낮추면 n 을 켠 채 n=1 이 실행된다), 정족수 미달이면 저장하지 않고 PROCESSING 유지, 짝수 n 은 기동 차단(바로 아래 홀수에 지배당한다). 측정은 새로 부르지 않고 접는다 — 회차 30개(1,260호출)를 n=1/3/5 로 묶어 2,940호출어치를 얻고, 다수결 규칙은 서비스 코드(judge_vote.combine)를 그대로 부른다. run_live.py 가 실경로 대조. 다수결은 작동했다(비결정성 24%→12%, 흔들리는 오분류 13종 완전 제거, 정상 판정 손실 0 — 교환비 처음으로 음수) 그런데 오분류 행은 안 준다(10.13→9.17, 범위 겹침, p=0.210) — 지운 13종이 원래 드물었고 동시에 70~97%이던 7종을 100%로 굳혔기 때문. 남은 7종은 -219 가 프롬프트로 못 움직인 것과 같은 목록이라 판정 층이 아니라 프리셋 description 축(back#136). fit 0건 Context 8.00 — 세 티켓 연속 불변. n=1 유지 |
다수결 측정, T57~T60, tools/judge_vote/ |
| I35 | 문서 색인 정합 검사 tools/check_docs_index.py — 전수 표의 T##·I## 중복, 파일 표 ↔ 파일 시스템의 고아/누락, 전수 표에만 있고 파일 표에 없는 문서(사고 5)를 ai-ci / check 스텝으로 판정. 연속성은 검사하지 않는다(T9·T10 은 back 레포에 있고 결번은 정상) 두 표가 일치하는지도 검사하지 않는다 — 누락만 본다(표 이중화가 07-31 사고 3 의 원인이라 문구까지 맞추라고 하면 문제를 규칙으로 승격시킨다. 구조가 해소되면 이 검사는 지운다). 누락 검사는 한 방향뿐이다 — 반대 방향(파일 표에 있는데 전수 표가 안 가리킨다)은 트러블슈팅 전수 표가 설계상 문서를 안 가리켜 정상 문서 22건이 위반으로 나온다. 셸이 아니라 Python 인 것은 pytest 가 같은 함수를 불러 CI 와 로컬이 같은 코드를 쓰기 위함. 새 잡이 아니라 check 안의 스텝인 것은 필수 상태 검사가 두 이름뿐이라 잡을 늘리면 실패해도 병합을 못 막기 때문. 메시지는 다음 빈 번호와 붙여 넣을 표 행까지 준다. 착수 시점 dev 위반 3건(고아) + 표 렌더링 6곳 정리. 조사 결과 표 이중화는 축소 불가 — 트러블슈팅 전수 표는 문서 링크가 0종이고 구현 전수 표는 파일 표 24개 중 17개만 덮으며 13행은 문서가 아닌 산출이다 |
색인 검사 리포트, T64·T65, tools/check_docs_index.py |
| I34 | 게이트웨이 오류 본문 마스킹 app/core/redact.py — 응답 본문 200자가 예외 메시지를 타고 다섯 곳의 로그와 트레이스백으로 나가던 것을 원천에서 막았다. 실제 AI API 로 네 경로에 오류 19건을 넣어 본문을 실측 — 자격 증명은 한 건도 에코되지 않았고(401 은 게이트웨이 고정 문구) endpoint 는 맨 호스트로 실리며, OpenAI 는 요청 값을 앞뒤 3자만 남기고 잘라 되돌린다(T57). 그래서 규칙은 「관측된 것을 지운다」가 아니라 「되돌아올 수 있는 자리를 막는다」다. 자격 증명이 endpoint 보다, 마스킹이 절단보다 먼저다. 본문은 지우지 않는다 — 실측한 벤더 400 본문에 마스킹 대상이 한 글자도 없었다 |
오류 본문 마스킹, failure-recovery §2.6, T57~T59 |
| I36 | AI API 게이트웨이 멀티모달 재고 — back#138 이 이미지 분석 흐름(Front → Spring → FastAPI)의 유일한 선행 조건으로 남긴 것. 1×1 PNG 를 OpenAI·Gemini·Anthropic 세 경로에 벤더 스펙 그대로(각 1회, 총 3호출) 실어 지원한다로 확정 — 셋 다 200 이고 이미지 내용에 실제로 답함(빈 응답·거부 아님). -205 의 실측 절차(작은 요청으로 크기 요인 배제, 자격 증명 마스킹 안전망)를 그대로 재사용 |
비전 재고, back#138 |
| I42 | 구현 리포트 파일 표 번호 컬럼 docs/implements/README.md + 단방향 검사 ⑤ tools/check_docs_index.py — -226 이 "④는 한 방향뿐"이라 명시적으로 남긴 반대 방향(파일 표에 번호가 있는데 전수 표에 없다)을 구현 리포트에서만 켰다. 번호 없던 7건 중 5건 신규 번호(I37~I41)·1건 기존 I30 링크 보완(candidate-threshold)·1건 「없음」 명시(ticket-audit-96-77, 문서 자신이 "산출이 아니라 확인 결과"라고 규정). 트러블슈팅은 그대로 — 전수 표가 설계상 문서를 안 가리켜 컬럼을 넣어도 대조 출처가 못 된다. 병합 중 병렬 PR(#81)과 I36 충돌이 실제로 나 T56 그대로 재현·해소 |
단방향 검사 리포트, tools/check_docs_index.py |
| I37 | 죽은 설정 키 전수조사 — .env·.env.example·config.py·배포 SealedSecret(8키) 대조 + 15개 Settings 필드 전부 런타임 sentinel 주입으로 반영 확인. 읽히지 않는 키 2개(PINLOG_JUDGE_MODEL·PRESET_CACHE_TTL_SEC) 는 저장소는 이미 정리돼 있었고 로컬 .env 잔재만 남아 있었다. .env 13 vs .env.example 12 전제 재확인 — 단일 차집합이 아니라 양방향(2:1)이고 둘 다 정상이라 example 변경 불필요. -197 계측이 벤더·모델을 이미 로그로 남김을 확인 |
죽은 설정 키 리포트, T66·T67 |
| I38 | 오류 응답 문구 분리 app/main.py — -221 이 남긴 한계. 두 핸들러 안에서 isinstance(exc, DatabaseTransientError|DatabasePermanentError) 하나로 database .../embedding upstream ... 를 가른다. 새 핸들러도 상태 코드 변경도 없다. static/05_AI_설계.md 에는 이 문구 자체가 없고(포인터만 있음) back AiSearchClient.translate 가 502/503 본문을 상태 코드만 보고 버리는 것을 직접 확인해 공용 계약 미반영으로 판단 |
오류 문구 분리 리포트, failure-recovery §2.5 「응답 본문」 |
| I47 | 단어형 질의 컷 격자 tools/search_cut/word_matrix.py·word_sweep.py + app/core/config.py(SEARCH_SIMILARITY_FLOOR_WORD 0.24 · SEARCH_WORD_QUERY_MAX_CHARS 5)·app/service/search_service.py(_cut(rows, query) 길이 분기) — -255 가 「컷 조정으로 회복된다」로 판정한 ai#87 을 닫는다. -255 의 22건으로는 답할 수 없었다 — 무관 통제가 치과 하나뿐이고, 컷 값 판단은 「무관 대역이 어디까지 올라오는가」가 전부라 그 대역을 1점으로 재면 -213 이 밟은 함정(무관 1건 → 간격 +0.2120, 15건 → -0.0176)을 그대로 밟는다. 무관 통제를 45행으로 늘리자 역전이 -0.0515 → -0.1687 로 깊어졌다. 기대 정답을 손으로 짝짓지 않고 본문 문자열 포함으로 계산한다 — 단어형은 정답이 여럿이라(신한→6건) 손으로 정하면 판정자 재량이 결과를 만들고, 이 기준이 ai#87 의 요구(「본문에 있는 말로 검색하면 나와야 한다」)와 정확히 일치한다. 부작용으로 같은 질의를 소유자 셋에게 던지면 통제가 공짜로 는다(정답 66행 · 교차 96행 · 무관 45행). 통제를 두 층으로 갈랐다(cross 는 같은 생활권 어휘라 무르다 — 합치면 「무관 통과」가 물러진다). 라벨을 요구하지 않는 대신 꼬리 제거율을 포기했다. 계약의 전제 정정 — 「문장형 회귀가 주 리스크」는 틀렸다(240/240 조합에서 누락 0, 컷을 푸는 방향이라 구조적). 진짜 손실은 무관 침묵이고 가르면 그것이 0 이 된다. 채택을 정한 지표는 「1위 손실」(회복률 96% 뒤에 「1위인데 0건」이 숨는다). r 은 무관 통과를 한 건도 못 줄이고(-213 의 구조적 성질 재현) 단어형 손실도 r=0.75 까지 0 이라 가르지 않았다 — 가르면 재지 않은 축을 코드가 만드는 셈. verify_live.py 에 분기를 다시 적어(구현 import 금지) 두 하한에서 갈리는 60행 + 문장형 27건 = 87/87 PASS, ai#87 실서버 회복 직접 확인(「그네」 0→6건·「비건」 0→1건 1위). T68(임베딩 비결정성 0.0044)이 이 티켓의 격자 간격·채택 마진과 같은 자릿수라 두 층을 갈라 답했다 — 격자 내부는 임베딩을 굳혀 훑으므로 애초에 노출되지 않고, 노출되는 것은 채택값의 절대 마진(0.0038)이다. 회차 3개 실측으로 이 조건 상한 0.000209 · 경계점 스프레드 0(순위 변동 0건) 확인, 격자 판정 전 구간 3회 일치. T68 의 처방을 그대로 따라 --repro 를 상시 관측으로 남겼다(한 번 재고 문서에만 적으면 다음 사람은 조건이 유효한지 알 방법이 없다). 왜 T68 보다 21배 작은지는 단정하지 않는다 — 그쪽은 프리셋 27개 중 3개만 갈렸고 이쪽 질의는 전부 짧아 텍스트 길이가 관련 있어 보이나 통제한 측정이 없다 |
단어형 컷 리포트, personal-search §6.1, tools/search_cut/, ai#87 |
| I46 | 프리셋 description·examples 개정 측정 tools/preset_desc/(6스크립트) + data/keyword_preset.yaml 5종 examples 개정 — E(examples 만) 채택 · D(description 만) 기각 · DE 보류. -210(τ)·-219(프롬프트)·-223(다수결)이 셋 다 같은 오분류 7행을 가리키고 끝난 뒤 네 번째 층(프리셋 텍스트) 을 잰 것이고, 네 티켓 만에 처음으로 채택이 나왔다. 설계의 핵심은 두 필드를 갈라 잰 것 — examples 는 임베딩 입력에만, description 은 임베딩+판정 프롬프트 양쪽에 들어가므로 합쳐 재면 어느 층이 움직였는지 모른다. 실제로 D 는 해로웠다(fit 0건 Context 8.20→10.00, 범위 분리 — 세 티켓 동안 σ=0 이던 지표가 처음 움직였는데 나쁜 쪽) 이고 그 해로움이 DE 안에서는 E 의 이득에 묻혀 안 보인다. E 는 오분류 11.00→6.90행(Δ-4.10·p=0.0001)·정상 손실 -0.80(p=0.16, 겹침)·교환비 0.20(-210 이 기각한 1.22 의 1/6)·순이득 부호가 양극단 모두 양수(+1.30/+5.30 — -219·-223 이 한 번도 못 넘은 줄). 사전 기준(범위 비중첩)은 base 최솟값 9 = E 최댓값 9 로 못 넘었고 그 자를 무르지 않은 채 독립 증거 넷(후보 층 정상 손실 0·홀드아웃 30건·부호 안정·5→10회 확장에서 효과 증가)으로 판단. 자기충족 방어 셋 — 수정 내용을 라벨과 무관한 원칙으로 정함(variants.py)·안 고친 22종 대조(split_score.py)·라벨 42건 밖 홀드아웃(probe.py). T68(임베딩 API 가 같은 배치로도 비결정적 — base2 바닥 조건으로 상시 관측, 후보 집합은 안 흔들림 확인)·T69(tau_grid/score.py 가 rank>K 로 밀린 행을 어느 칸에도 안 셈 — score.py 는 -210 기준점이라 안 고치고 rank_loss.py 로 갈라 셈) 발견. 라벨은 한 줄도 안 더함 |
프리셋 개정 리포트, T68·T69, tools/preset_desc/ |
| I45 | 검색 재현율 프로브 tools/search_cut/recall_probe.py — 대상 질의 4건에 비교군 18건을 짝지어 「짧아서인가·약어라서인가·부분어라서인가」를 가른다(AI API 배치 1회). 본문을 고정하고 질의 길이만 바꾸는 축과 어느 본문에도 없는 짧은 질의(치과) 를 계약의 최소 비교군에 더한 것이 판정을 갈랐다 — 후자가 없으면 「2자의 0.24 가 낮은 값인가」를 판단할 기준이 없다. 판정을 두 층(cut_verdict 무엇이 잘랐나 / cause 컷을 풀면 회복되는가)으로 나눈 이유는 같은 「τ_abs 가 잘랐다」가 풀면 1위인 스팟과 풀어도 8위인 부캠에 함께 붙기 때문. --replay 는 굳힌 행렬로 판정만 다시 내고(AI API·DB 미호출) --lengths 는 본문 길이 상관을 낸다 |
재현율 판별, tools/search_cut/ |
| I48 | dev 배포 미반영 구간 판정 + 새 프리셋 봉인 값 preset-ab321360b0df — 산출물은 값 하나이고 나머지는 판정이다. ① ai-image / publish 는 ai-ci.yml 의 image-publish job(별도 워크플로가 아니라 gh api .../workflows 에 안 보인다)이고 if: push && refs/heads/main 이라 dev push·PR 에서 구조적으로 SKIPPED — 설계 근거 넷(ai-ci.yml:154-156 주석 · test_ci_image_publish_contract.py:48 이 두 조건을 문자열로 잠금 · infra ai-image-update.yaml:44 의 test "$SOURCE_BRANCH" = main · CONTRIBUTING.md:48-50)이라 ai 레포 안에 고칠 것이 없다. ② apps/dev/ 의 dev 는 클러스터 이름이고 pin 대상은 ai 의 main HEAD — 두 「dev」가 겹쳐 오해를 만든다. ③ ai-image-update 04:53Z success 는 create-infra-pr 의 RED 스텝이 통과해 changed=false 로 끝난 것(= 갱신할 것이 없음)이며, 자동화 자체는 00:37Z 에 되살아나 infra#172(00:39Z 병합)로 tag 를 d317f563→4d667c3 로 이미 한 번 갱신했다. ④ 봉인 값 산출 방식을 실측으로 확정 — infra docs/ai-dev-prerequisites.md:137-139 의 64자와 de6e995 파일 해시가 전량 일치하므로 정규화·항목 단위가 아니라 파일 전체 바이트 SHA-256 의 앞 12자다. .gitattributes LF 고정이라 OS 무관하고 chart 제약 셋(비어있지 않음·DNS label·Job 이름 63자)을 통과한다(옛 값과 길이가 19자로 같다). ⑤ bootstrap.version 은 update_ai_image.py 의 갱신 범위 밖(FIELD_RE 가 repository|tag|digest 한정)이라 사람이 갱신해야 하고 틀려도 실패하는 검사가 없다. ⑥ 미반영 커밋 10개 중 -205(ai#78 로그 유출 차단)가 최초 목록에서 누락돼 있었음을 정정 |
dev 배포 구간 리포트, ai-ci.yml, tests/test_ci_image_publish_contract.py |
| I49 | AI API 이미지 측정 하네스 tools/gms_image/(5스크립트) + 회차 기록 .gms_image/axis-{a,b}.jsonl — 설계의 핵심은 치수와 바이트를 따로 움직인 것이다. 실제 사진은 치수가 크면 바이트도 커서 -253 의 세 가설(A 게이트웨이 가산 · B base64 를 텍스트로 셈 · C 벤더 자체 규칙)이 전부 같은 곡선을 낸다 — 합성 PNG 의 노이즈 줄 수로 512×512 를 유지한 채 2 KB↔72 KB 를 만들어 얽힘을 풀었다. 판정은 두 기계적 검사(바이트 불변 · 치수 반응)로만 하고 벤더 공개 공식은 판정에서 분리했다(넣으면 「공식대로니 정상」이라는 순환). 가설 A 기각의 근거는 경로를 가로지른 대조 — Gemini 만 보면 272 로 평탄해 A 처럼 보이지만 같은 게이트웨이의 OpenAI(8,523→25,524)·Anthropic(39→1,407)이 치수에 반응하므로 게이트웨이 상수가 아니다. 비용 손잡이는 벤더 선택이 전부 — 같은 1024×1024 가 gpt-4o-mini 25,524 · haiku-4-5 1,407 · gemini-2.5-flash-lite 272 로 94배 차이다. 축 A 는 404·401·403 이 하나도 안 나왔다 — AI API 는 모든 거부를 400 + 자체 봉투로 내고, 대조군으로 넣은 GET .../models 가 세 벤더 모두 「Model not found in request」를 내면서 게이트웨이가 경로가 아니라 본문의 model 로 라우팅한다는 구조가 드러났다(그래서 모델 목록 조회 자체가 불가능하고, 본문 상한 오진도 같은 뿌리다). 호출 상한을 코드에 박고(축 A 20 · 축 B 30, 양쪽 소진) 응답을 한 건씩 append 한다 — 세션이 죽어도 쓴 쿼터가 남는다. report.py --replay 는 AI API 를 안 부른다(판정 규칙 변경과 T27 쿼터 변동이 섞이면 결론이 왜 바뀌었는지 알 수 없다). 못 잰 것 — detail:"low"(계획 조건이 전부 본문 상한에 걸렸다) · 상한의 정확한 값(구간까지) · 분당/일일 한도(429 가 0건) · p95(n=5) |
이미지 측정 리포트, tools/gms_image/, 비전 재고 I36 |
| I50 | 표시명 개정 배포 체인 기록 — 산출물은 문서 하나이고 내용은 판정이다. ① 체인 여섯 단계의 소관을 갈랐다(우리 ai#102·ai#104 / 자동화 image-publish / 인프라 infra#185 / 클러스터 sync / 화면). image-publish 가 별도 워크플로가 아니라 CI 안의 job 이고 main push 조건이라 dev 병합만으로는 이미지가 안 만들어진다(전수 근거는 I48). ② 미결 판정 하나를 실측으로 닫았다 — 봉인 값이 개정 전 값 그대로인 채 배포됐는데 새 표시명이 화면에 나왔다. 따라서 그 값은 판 표기이지 재실행 조건이 아니다(BeforeHookCreation 이 같은 이름 Job 을 지우고 다시 만들고, 태그가 바뀌면 sync 가 돌며, 적재가 멱등이다). 그 전에는 중앙이 반대로 판정했고 독립 검증이 논거만 반증한 채 **클러스터 관측 경로가 없어 「확인 불가」**로 끝나 있었다. 다만 표기가 낡은 것은 기록 정합성 문제로 남는다 — 배포를 막지 않을 뿐이고 틀려도 실패하는 검사가 없다. ③ 릴리스가 막히는 두 곳을 절차로 적었다 — base 검사는 release/*·hotfix/* 만 허용하므로 dev 를 그대로 열지 않고(ai#103 이 그렇게 막혀 닫혔다), strict 는 BEHIND 를 거부하므로 main 을 먼저 병합해 해소한다(그 병합 커밋 032e391f 가 ai#104 에 남아 있다). 둘 다 CI 를 다시 기다리게 한다. ④ 갱신 자동화의 예정 회차가 릴리스 병합 직전에 돌아 헛돌았고 갱신을 만든 것은 수동 실행이다 — success 두 번의 뜻이 다르다(T71 과 같은 성질). ⑤ 확인 수단은 화면뿐이라 「아직인가 실패인가」가 갈리지 않는다 — 실제로 한 번 오판했다가 재확인에서 뒤집혔다 |
배포 체인 리포트, I48, T70~T72 |
| I51 | 짧은 질의 층 프로브 tools/search_cut/layer_probe.py + 경계 행렬 boundary_matrix.py·boundary_sweep.py + .search/boundary_grid.json(1,128행)·layer_probe.json — 산출물은 하네스 셋과 판정이고 코드는 고치지 않았다. ① 층을 코드로 확정했다 — 계약이 후보로 준 「질의 전처리」와 「후보 0건 폴백」은 존재하지 않는다(schema/search.py:9 는 min_length=1 뿐이고 정규화가 없어 원문이 그대로 도달하며, _cut 이 0건이면 0건이 그대로 응답이다). 자르는 것은 search_service.py:111-115,122 둘뿐이고 LIMIT 은 이 데이터에서 한 건도 안 자른다(후보 17·11·6건). 실서버 대조 22/22 일치 — 재구성에 _cut·_is_word_query 를 다시 적었다(구현 import 금지, -213 규칙). ② -266 이후 남은 실패는 셋뿐이고 서로 다른 층이다 — 신한(③ τ_abs 0.2301<0.24·6위)·부캠(③ 0.2105·8위)·신한 부캠(④ r 0.3187<0.3520·4위). 셋째는 τ 를 넘고 r 에만 걸리므로 경계 정의를 어떻게 바꿔도 안 풀린다(-266 이 r 을 「참여하지 않는다」로 가르지 않았다). ③ 「2자가 안 걸린다」는 부정확 — 2자 7건 중 5건은 걸리고(우주 0.4200 1위·공원 0.3672 1위) 못 걸리는 둘은 컷 전 순위가 6·8위다. ④ -266 이 「둘이 갈리는 질의가 행렬에 없다」고 적은 대역을 만들었다 — 본문 문장 내 인접 어절쌍 전량을 spaced/joined 짝으로 내면 기대 정답이 같아 차이가 전부 「공백 하나」에 귀속된다(쌍 176종 × 2형태 × 소유자 3명 = 1,128행, AI API 376건). 질의를 고르지 않아 -266 이 한계로 적은 선정 재량이 없다(남는 재량은 「두 어절 모두 2자 이상」 하나). ⑤ 손실은 C 대역(무공백·길다) 에 몰려 있다 — 현행 16/122·1위 손실 4 가 어절 수 정의에서 3/122·1 이 되고 A·B·D 는 안 움직인다. 그런데 내용어 초점 집합 36행에서는 여섯 정의 전부 누락 0 이라 두 표가 어긋난다 — 회복되는 것이 기능어 쌍(당시자주 0.1909·같은분위기가 0.2170)이라 사용자 이득이 관측되지 않았다. ⑥ 새 관측 둘 — 공백을 떼면 유사도가 내려간다(198짝 평균 -0.0385·중앙값 -0.0279·172/198 하락)는 것과 전각 공백은 더 내려간다(신한 부캠 0.3187→0.2753 으로 ④ r 이 아니라 ③ τ 에서 잘린다). 후자는 -266 이 「측정이 아니라 안전 방향으로의 판단」이라 적은 대역이고 그 판단이 여기서는 손해로 나타난다(3건 표본). ⑦ 처방 후보 여섯을 비용·부작용과 함께 냈고 「안 바꾸는 안」을 포함했다 — 고르는 것은 이 티켓의 일이 아니다. ⑧ 계약이 「네가 재는 것이 첫 측정」이라 못박았으나 -255·-266 이 선행하고 후자는 코드까지 병합돼 dev 배포 tag(7e72826)에 들어 있으므로 세 번째다 |
경계 리포트, I45, I47, tools/search_cut/ |
| I53 | Keyword fusion 1단계 실측 tools/search_cut/fusion_sweep.py 격자 — 방식(binary·conf·idf·RRF)×weight×floor(0.25/0.30/0.35/0.40)×RRF cutoff(0~0.033). 신한 복구와 무관 노출 증가가 같은 계산에서 나온다(가중합은 컷을 부스트 점수에 걸어 경계 아래 정답과 무관 후보를 똑같이 끌어올린다). RRF 는 cosine eligibility gate 로 무노출을 지키지만 벡터 1위가 키워드 순위 합산에 밀려 문장형이 퇴행한다. floor 0.35 에서 무관 무노출이 baseline 복원되고 binary w=0.05 만 P48 §6.1 전 조건 통과 — Preset 후보가 floor 를 넘는 단어형 질의가 12/66 뿐이라는 대가(미반영 54건 = -338 설계 입력). 사례 4건 개별 보고(신한·부캠 미회복 확정). 픽스처 27종·가드 무오발동·baseline 재현 일치 |
실측 리포트, T73~T78, P48, P49 |
| I54 | 문자열 병합 규칙 하네스 tools/search_cut/lexical_matrix.py·lexical_sweep.py — 본문 매치를 (질의 × 소유자 × Record) 로 굳히고(본문 미저장·매치 여부만) 게이트×병합 격자를 오프라인으로 훑는다. 스냅샷 DB(:25432) 가드. 측정 결과: 문자열 매치 94건이 전부 정답 세그먼트에만 발생, 단어형 정답 라벨이 부분일치 정의라 재현율은 구조적으로 유리(순환성 명시), 병합 방식 차이는 RRF·앞고정 > 뒤추가. 단독 추가 7건 · 빈 결과 1건 해소 |
규칙 리포트, I53, P49 |
| I55 | 재작성 효과 프로브 tools/search_cut/rewrite_probe.py — 실제 RewriteClient(운영 설정 체인) 호출 → 재작성문 임베딩 배치 1회 → 스냅샷 DB(:25432) 유사도 → 현행 컷 재구성(단어형/문장형 분기는 재작성문 기준, 서비스 미import 재구성 규칙). 재작성문 자체를 artifact 에 기록 — 무엇으로 바뀌었는지가 판정 근거의 절반. 원문 기준 값은 굳힌 행렬의 컷 재구성으로 얻어 AI API 호출 최소화, 재구성 무노출 11/15 가 기존 관측과 일치(교차 확인). 게이트(should_rewrite 재구성) 반영 후에는 게이트 통과 질의만 재작성·재임베딩 — 걸린 질의는 원문 값이 곧 결과(서비스와 동일). 강등 0건·전 질의 1순위 벤더 응답 관측 |
효과 리포트, I54, P49 |
| I56 | 키워드 재정렬 하네스·런타임 tools/search_cut/fusion_rerank_sweep.py(재정렬 전용 격자 + 신선도 가드 + 후보 불변 검사) · fusion.rerank()(순수 함수) · rerank_verify_live.py(실서버 on/off 5판정) · app/service/search_service.py(_rerank_by_keyword — 질의 임베딩 재사용·추가 모델 호출 0) · app/repository/context_keyword_repo.keywords_for_records · SEARCH_KEYWORD_RERANK_* 4키(기본 off·floor 0.35·w 0.05·top_k 3) — 채택값은 재정렬 전용 구조 재측정으로 확정(I53 과 숫자가 같지만 다른 측정), 결정성 회차 JSON 바이트 일치, 실서버 대조 93건 5판정 전부 통과. 번호 잠정 I56 — 병렬 잠정 I55 충돌을 통합 병합에서 재확정(설계된 절차), 최종 확정은 dev 병합 직후 |
재정렬 리포트, I53, P49, tools/search_cut/ |
| I57 | 표시명 정합 회복·재측정·게이트 재검증 — app/bootstrap/load_presets 재적재로 프리셋 표시명(-292 명사형)을 정본에 맞추고, 그 조작이 무효화한 측정을 전부 다시 뜬다. 조작 범위는 md5 4종 대조로 고정(프리셋 임베딩만 변경 · Context 임베딩·판정 상태·context_keyword 83행 불변). keyword_matrix 재생성 → fusion_rerank_sweep 격자 재실행(결정성 2회 일치) → 게이트 3 phase 전량. 채택값 유지(퇴행 없이 단어형 개선) · 문장형 개선 소멸(floor 0.40 구간으로 이동) · 기준선 인접 쌍 9건이 임베딩 흔들림 폭 이내라는 한계. keyword_matrix.py 포트 가드를 시연 DB(:15432)에서 스냅샷(:25432)으로 정정 |
재검증 리포트, I56, P49 |
| I58 | 결합 신뢰도 게이트 threshold 재측정 하네스 tools/search_cut/gate_sweep.py — 기존 행렬(matrix.json·word_grid.json·recall_probe.json·lexical_matrix.json·keyword_matrix.json)만 읽어 S1(벡터)·S2(문자열, 채택 게이트 G1)·S3(키워드, 채택값 binary·floor 0.35·top_k 3) 신호를 재구성하고 threshold 격자(0.0·0.16 |
재측정 리포트, OFFTOPIC-CONFIDENCE-GATE-HANDOFF-DRAFT.md §4.3, I56, tools/search_cut/ |
| I59 | 검색 응답에 keywordMatched 필드 추가(Jira 작업) — _rerank_by_keyword(I56)가 이미 계산하지만 정렬에만 쓰고 버리던 Preset 매치 여부를 tuple[list, set[int]]로 바꿔 응답까지 싣는다. similarity는 원래 코사인 그대로 두어 정렬 점수 비노출 계약(③)을 지켰다. 재정렬이 생략되는 모든 경로(off·오류·후보 없음)에서는 빈 집합이라 필드가 자연히 False다. 결합 신뢰도 게이트(중앙 조정 세션 OFFTOPIC-CONFIDENCE-GATE-HANDOFF-DRAFT.md §4, back 레포 Jira 작업)가 쓸 S3 신호를 준비하는 작업이다 |
필드 추가 리포트, I56, test_search_rerank.py |
| I60 | 재정렬 후보 불변 계약과 게이트 계약 분리(Jira 작업) — test_search_rerank.py가 고정하는 다섯 계약(특히 ② 후보 불변)이 재정렬 자신의 것이며 결합 신뢰도 게이트(back 레포 Jira 작업, BD-52)의 계약이 아님을 모듈 docstring에 명시하고, test_rerank_never_gates_a_weak_unsupported_candidate로 "게이트라면 지웠을 후보도 재정렬은 지우지 않는다"를 실행 고정. 런타임 코드 변경 없음 |
경계 리포트, I59, OFFTOPIC-CONFIDENCE-GATE-HANDOFF-DRAFT.md §4.5 |
I6·I7·I8은 백엔드 아티팩트라 back 레포
docs/ai/implements에 있습니다.