Skip to content

Latest commit

 

History

History

implements

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

구현 리포트 (Implements)

무엇을 만들었고 어떻게 검증했는지 기록합니다. 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.58330.6667 퇴행으로 기각. 무관 세그먼트는 전 조합에서 BASE 와 동일(구조적 불변 실측 확인·스크립트 가드). floor 0.350.36 지표 동일로 T68 흔들림에 안전. 런타임: 기본 off 플래그·keyword_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/ 분리를 검토한다.

구현·산출 — 전수 (AI 소유)

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.160.40)를 훑는다. 0.35에서 정답 손실 0(문장형 12·단어형 66·프로브 22 전부)이면서 무관 노출이 크게 준다(단어무관 23/45·타인소유 55/96 신규 침묵) — 안전 구간(0.250.37) 안의 최적값(0.36)과도 사실상 같다. DB·AI API 호출 0회, 결정성 확인(같은 입력 2회 실행 바이트 일치) 재측정 리포트, 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에 있습니다.