Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/WORKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,6 @@
| 2026-08-03 | 표시명 개정(`S15P11A705-292`)이 코드에서 운영 화면까지 간 **배포 체인을 기록했다**(I50). **체인 여섯 단계의 소관을 갈랐다** — 우리(`ai#102` dev · `ai#104` 릴리스) · 자동화(`image-publish` 는 별도 워크플로가 아니라 CI 안의 job 이고 `main` push 조건이라 **`dev` 병합만으로는 이미지가 안 만들어진다**) · 인프라 소관(`infra#185`) · 클러스터 sync · 화면. **핵심은 미결 판정 하나를 실측으로 닫은 것이다** — 프리셋 봉인 값이 개정 전 값 그대로인 채 배포됐는데 새 표시명이 화면에 나왔다. 따라서 그 값은 **판 표기이지 부트스트랩 재실행 조건이 아니다**(`BeforeHookCreation` 이 같은 이름 Job 을 지우고 다시 만들고, 이미지 태그가 바뀌면 sync 가 돌며, 적재가 멱등이다). 그 전에는 중앙이 「값이 같으면 재실행되지 않는다」로 판정했고 독립 검증이 논거를 반증했으나 **클러스터 관측 경로가 없어 「확인 불가」로 끝나 있었다** — 배포가 답을 냈다. **그래도 표기가 낡은 것은 기록 정합성 문제로 남는다**(배포를 막지 않을 뿐이고 틀려도 실패하는 검사가 없다). **릴리스가 두 번 막혔고 둘 다 다음에 그대로 다시 만난다** — base 검사는 `main` base 를 `release/*`·`hotfix/*` 로 한정하므로 `dev` 를 그대로 열면 CI 가 막고(`ai#103` 이 그렇게 닫혔다), strict 상태 검사는 BEHIND 를 거부하므로 `main` 을 먼저 병합해 해소한다(병합 커밋 `032e391f`). 갱신 자동화의 예정 회차가 릴리스 병합 **직전**에 돌아 변경 없이 끝났고, 갱신을 만든 것은 수동 실행이라 **`success` 두 번의 뜻이 다르다.** 확인 수단이 화면뿐이라 「아직인가 실패인가」가 갈리지 않아 한 번 오판했다가 재확인에서 뒤집혔다. **값은 옮기지 않고 좌표만 적었다**(`T70` 적용) | [배포 체인 리포트](implements/2026-08-03-preset-display-name-deploy-chain.md), [I48](implements/2026-08-03-dev-deploy-gap.md), [T70~T72](troubleshooting/2026-08-03-repeat-incidents.md) |
| 2026-08-03 | 외부 리뷰 문서를 **P47 제안 문서로 다시 썼다** — 프리셋의 표시 라벨·축 정의·스키마 개정안(`S15P11A705-228`·`-269`·`-270`·`-271`·`-292`, `ai#90` 후속). **proposals 최초의 `Proposed`** 다 — §7 이후가 미실행이고 §11 실측이 없다. **다만 §6(표시 라벨)은 문서를 쓰는 사이에 반영이 끝났다** — `-292` 가 `display_name` 을 명사·명사구로 통일해 `ai#102`(`dev`)·`ai#104`(`main`)로 나갔고, 그래서 그 절만 제안이 아니라 **기록**으로 다시 썼다(제목에 「반영 완료」 · 표의 「권장 표시명」을 「반영값」으로 · 정본은 문서가 아니라 `data/keyword_preset.yaml` 임을 명시). **초안과 실제가 갈린 셋을 확정으로 남겼다** — `ALONE` 은 초안의 `1인` 을 기각하고 `혼자` 를 유지했고(축의 나머지가 관계어인데 수량 표현만 이질적이다), `TRENDY` 는 `examples` 가 유행이 아니라 미감을 가리키므로 `감성` 으로 확정했으며, `RETRO` 는 **초안에 없던 결정**으로 `복고` 가 됐다(같은 축의 어휘 층위 통일 · `description` 과 정합). **선결 조건이 통째로 바뀌었다** — 「프리셋을 고치면 기존 판정을 전부 재처리한다」로 정해지면서 「판을 구분할 경로」(행 `version` 을 올릴 경로가 없다는 사실, `-269`)가 선결에서 §3-F 로 내려갔고, 그 자리에 **「재처리할 수단이 없다」**가 들어갔다: 완료 State 를 되돌리는 코드가 없고 · 재스캔이 잡는 상태에 완료가 없으며 · 한 번에 밀면 게이트웨이 한도에 걸려 재시도 예산이 소진되면 실패로 굳는다(부재의 범위는 `ai#100`). **「정할 것」이 아니라 「만들 것」이라 §13 0단계가 구현 항목이 됐고**, 같은 이유로 §10.3 세트 release 는 운영 용도가 빠지고 평가 재현만 남아 우선순위가 내려갔다. **원본의 다섯 곳을 정정했다**: ① Feed 표시 정렬을 절째로 빼고 `back` P46 을 가리켰다(원본 제안이 그 결정이 **명시적으로 기각한** 방향이었다), ② 「CI 에서 검증되지 않는 것」 목록을 없애고 방향과 「테스트 코드가 정본」만 남겼다(이미 있는 테스트를 없다고 적고 있었다), ③ 배포 서술을 hook 메커니즘 존재라는 구조 사실로 줄였다(나머지는 `infra` 소관), ④ `version` 을 「세트 release 와 분리」가 아니라 판 식별 문제로 재배치, ⑤ 표시명 오염을 「임베딩 입력」이 아니라 **임베딩·판정 프롬프트 양쪽**으로 고쳤다 — 그래서 이 변경은 임베딩 실험이 아니라 프롬프트 실험 대상이다. **구조 사실 일곱을 보강했다**(개정에 기존 판정 재처리가 따라붙는다 · 후보 슬롯 경쟁으로 프리셋 단위 개선이 전역에서 상쇄된다 · 저빈도 프리셋 처분이 라벨 개정보다 앞선다 · 표시명 개정이 백엔드 조회의 정렬과 중복 흡수에 파급된다 · **`is_active` 를 끌 경로가 없어 tombstone 은 구멍 메우기다** · 공용 계약 개정 단계 · 임베딩 비결정성 때문에 1회 측정으로 채택을 가르지 않는다). **값을 쓰지 않았다** — 측정 수치·현행 상수·행 번호·배포 상태를 전부 출처 참조로 바꿨고 남은 숫자는 §6.2 의 반영값뿐인데 그것도 YAML 이 정본임을 표 앞에 못박았다. 문서가 값을 안고 있으면 값이 바뀔 때 문서만 낡는다. **`§6` 이 실측 없이 먼저 나간 것은 §14 에 감수 항목으로 남겼다** — 표시 계층만 손댄 범위였기 때문이고, 대가로 라벨 개정이 판정에 얼마나 파급됐는지는 재지 않은 채 §11 에 남는다 | [P47](proposals/P47-keyword-preset-label-axis.md), [근거 리포트](implements/2026-08-03-preset-description.md), [T68·T69](troubleshooting/2026-08-03-preset-description.md) |
| 2026-08-07 | 검색 응답에 `keywordMatched` 필드를 추가했다(`S15P11A705-399`). 키워드 재정렬(`_rerank_by_keyword`, P49 §4)이 이미 계산하지만 정렬에만 쓰고 버리던 match 여부를 응답까지 살렸다 — 재정렬을 `tuple[list, set[int]]`로 바꿔 match 한 Record id 를 함께 반환하고, 재정렬이 생략되는 모든 경로에서는 빈 집합이라 자연히 `False` 다. `similarity` 는 원래 코사인 그대로 두어 정렬 점수가 새어 나가지 않는다는 기존 계약을 지켰다. 결합 신뢰도 게이트(`S15P11A705-400`)가 쓸 S3 신호를 준비하는 작업이다 | [리포트](implements/2026-08-07-search-keyword-matched-field.md) |
| 2026-08-07 | 재정렬 후보 불변 계약(`test_search_rerank.py` ②)과 결합 신뢰도 게이트(back 레포 `S15P11A705-400`, BD-52)의 계약을 분리 명시했다(`S15P11A705-402`, 중앙 조정 세션 인계 문서 §4.5가 요구한 검증). 재정렬은 후보를 안 지우지만 게이트는 정반대로 의도적으로 지운다 — 같은 파일에 다섯 계약만 있으면 게이트도 후보 불변을 지켜야 한다는 오독이 생긴다. docstring에 "이 파일이 고정하지 않는 것" 절을 추가하고, 게이트라면 제외했을 모양의 후보(기존 컷은 통과·게이트 임계값 미만·신호 없음)도 재정렬은 순서만 밀 뿐 지우지 않는다는 것을 새 테스트로 고정했다. 이 브랜치는 `-399`(I59) 병합 전 갈라져 같은 색인 누락을 독립적으로 I58 로 잡아 두고 있었다 — `dev` 병합에서 두 번호가 부딪혀(`-399` 가 먼저 `I59` 로 확정 병합) 이 리포트는 **I60 으로 재확정**했다(T60 과 같은 패턴, `check_docs_index.py` 의 번호는 병합 시점 이후에 확정한다는 원칙대로) | [경계 리포트](implements/2026-08-07-rerank-gate-contract-boundary.md) |
| 2026-08-07 | 결합 신뢰도 게이트(중앙 조정 세션 인계 문서 `OFFTOPIC-CONFIDENCE-GATE-HANDOFF-DRAFT.md` §4) 의 threshold 후보 0.35 를 오프라인으로 재측정했다(`S15P11A705-401`). 0.35 는 원래 `SEARCH_KEYWORD_RERANK_FLOOR`(질의-Preset 코사인 판정값)에서 가져온 값이라 이 용도(S1 단독·저유사도 결과를 숨기는 게이트)로는 검증된 적이 없었다. `gate_sweep.py` 로 문장형 정답 12·단어형 정답 66·진단프로브 22·무관 60·타인소유 96건에 게이트 규칙을 적용해 보니 threshold 0.35 에서 정답 손실 0 을 유지하면서 무관 노출이 크게 줄었고(단어무관 23/45·타인소유 55/96 신규 침묵), 손실 0 구간의 최적값(0.36)과도 사실상 같았다. **최초 실행에서는 프로브 6 건이 손실로 나왔는데, 원인은 `recall_probe.json` 이 질의별이 아니라 최상위에 `user_id` 를 두는 형식을 놓친 것이었다** — 고치자 손실이 threshold 0.38 까지 0 건으로 나왔다. 0.35 채택을 권고한다 | [리포트](implements/2026-08-07-gate-threshold-remeasure.md) |
| 2026-08-07 | 401 리포트 작성 뒤 `test_docs_index.py`가 `docs/implements/README.md`의 색인 두 표(개별 리포트·구현·산출 전수) 모두에서 I58(`2026-08-07-gate-threshold-remeasure.md`) 행이 빠진 것을 잡았다 — 리포트를 커밋하면서 색인 갱신을 놓친 것. 두 표 모두에 행을 추가해 정정했다 | [gate_sweep 리포트](implements/2026-08-07-gate-threshold-remeasure.md) |
24 changes: 24 additions & 0 deletions docs/implements/2026-08-07-rerank-gate-contract-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 재정렬 후보 불변 계약과 결합 신뢰도 게이트 계약의 분리

- **티켓**: S15P11A705-402
- **날짜**: 2026-08-07
- **성격**: 문서·테스트 경계 명시. 런타임 코드는 바꾸지 않는다.

## 배경

`OFFTOPIC-CONFIDENCE-GATE-HANDOFF-DRAFT.md`(중앙 조정 세션 인계 문서) §4.5는 착수 시 필요한 검증 중 하나로 다음을 명시했다.

> 기존 계약 테스트(§4.1에서 인용한 "정렬은 후보를 추가·제거하면 안 된다")는 재정렬 로직의 계약이지 이 새 게이트의 계약이 아니라는 것을 별도로 명시해야 한다 — 헷갈리면 안 되는 지점이다.

`tests/test_search_rerank.py`가 고정하는 다섯 계약 중 ②("같은 요청·같은 limit에서 on/off의 후보 Record id 집합이 같다")는 재정렬(`_rerank_by_keyword`)의 것이다. 결합 신뢰도 게이트(S15P11A705-400)는 back 레포에 구현됐고 정확히 그 반대 성질 — S1 신호만 있고 유사도가 낮은 후보를 **의도적으로 제거한다** — 을 가진다. 이 파일만 읽으면 "재정렬은 후보를 안 지운다"는 규칙이 게이트에도 적용돼야 한다는 오독이 생긴다.

## 변경

- `tests/test_search_rerank.py`의 모듈 docstring에 "이 파일이 고정하지 않는 것" 절을 추가했다. 다섯 계약이 재정렬 자신의 것이며, 결합 신뢰도 게이트는 별도 저장소(`back`)의 별도 마지막 단계(BD-52)라는 것을 명시한다.
- 새 테스트 `test_rerank_never_gates_a_weak_unsupported_candidate`를 추가했다. 게이트라면 제외했을 모양의 후보(기존 컷은 통과하지만 게이트 채택 임계값보다 낮은 유사도, S2·S3 신호 없음)를 재정렬에 넣고, 그 후보가 순서만 밀릴 뿐 결과에서 사라지지 않는 것을 확인한다.

## 검증

- `pytest tests/test_search_rerank.py` — 신규 1건 포함 전체 통과.
- `ruff check .` · `pytest --cov` 전체 · `tools/check_coverage_gate.py` — line 94.64%·branch 85.00%, 둘 다 통과.
- `pytest tests/test_docs_index.py` — 이 작업 도중 `docs/implements/README.md`의 두 색인 표(개별 리포트·구현·산출 전수) 모두에 S15P11A705-399 리포트 행이 빠져 있던 것을 발견해 함께 고쳤다(원래 이 티켓의 범위는 아니었으나, 색인 검사가 이 티켓의 변경으로 인해 실행되면서 잡았다).
Loading
Loading