엔지니어링 로그
대부분의 프로젝트 연혁은 무엇을 출시했는지 적는다. 이 페이지는 무엇이 틀렸는지를 적는다 — 캘리브레이션을 통째로 약속하는 시스템에서 흥미로운 사건은, 시스템이나 그 측정이 거짓말하고 있었다고 밝혀진 순간들이기 때문이다.
이 페이지는 손으로 써도 되는데, 이 리포의 다른 문서 대부분은 그렇지 않다. 나머지는 코드의 현재 상태를 서술하므로 코드가 움직이는 순간 썩는다 — 그래서 그 문서들은 자기가 서술하는 소스에 앵커로 묶여 매 푸시마다 검사받는다. 날짜별 기록은 다르다. 어느 날에 무엇이 참이었는지를 말하고, 지나간 날은 바뀌지 않는다.
무엇이 그것을 잡았나
아래 20건을 무엇이 실제로 그 결함을 드러냈는지로 나눴다. 개수보다 모양이 중요하다: 자동 검사는 인프라 문제를, 측정은 데이터 문제를 잡았고, 가장 값비싼 결함 하나 — 몇 주 동안 모든 자동 검사를 통과하고 있던 그것 — 은 사람의 한 문장이 잡았다. 가장 최근 것들은 전부 같은 방법으로 나왔다: 주장을 그것이 서술하는 대상에 대 보는 것. 맨 끝 항목도 그렇다 — 사람의 질문 하나가, 만들어 놓고 읽는 쪽을 안 만든 기능을 드러냈다.
7월 — 돌고 있지 않던 검사들
한 번도 실행된 적 없는 테스트
2026-07-10 · ci(nexus): run the DB-backed tests — and repair the fixtures that never ran
DB 기반 통합 테스트는 “빨간불이 없다”는 의미에서 건강해 보였다. 그것들은 한 번도 실행된 적이 없었다: 픽스처 스코프 불일치로 setup 단계에서 죽었고, 러너는 그것을 스위트가 견디는 에러로 보고했다. 몇 달치의 “통과”가 실제 DB 를 건드리는 경로에 대해 아무것도 말해 주지 않고 있었다.
수리는 마이그레이션을 적용한 실제 서버에 전체 스위트를 돌리는 Postgres CI 잡을 세웠다. 남은 규칙: 스킵된 테스트는 없는 테스트다. 그리고 무언가 잡는 것을 한 번도 본 적 없는 검사는 동작이 확인된 검사가 아니다. 그 뒤로 가드를 추가하면 일부러 한 번 깨뜨려 빨간불이 되는지 확인한다.
테스트 실행이 실제 코퍼스를 지웠다
2026-07-10 · feat(nexus): document lifecycle across web, MCP and CLI + a guard against wiping a real DB
스위트는 테스트 사이에 테이블을 TRUNCATE 한다. 스크래치 DB 라면 옳은 동작이다. 그것이 개발 DB 를 겨누고 있었고, 그 안의 코퍼스가 사라졌다.
처방은 “연결 문자열을 조심하자”가 아니었다. 이제 DB 가 스스로 일회용임을 선언해야 한다 — 스위트는 마커 테이블을 찾고, 없으면 하드 실패로 종료한다. 조심은 기제가 아니고, 거부가 기제다.
8월 — 틀린 것은 측정 쪽이었다
문서에 적힌 숫자가 실행이 낸 숫자가 아니었다
2026-08-04 · docs: correct the coverage figure — 232 chunks was wrong, the run says 10
커버리지 숫자 하나가 출력이 아니라 기억에서 문서로 옮겨져 있었다. 실행을 다시 읽으니 자릿수가 넘게 틀렸다. 그 자체로는 작은 일이고, 여기 적는 이유는 이것이 패턴의 시작이었기 때문이다: 이후 2주 동안 시스템보다 계측기가 더 자주 틀렸다.
명세해 놓고 건너뛴 대조군이 판정을 뒤집었다
2026-08-06 · fix(ko-eval): run the calibration control that was specified and skipped - it overturned a judgement
평가 설계에 음성 대조군이 있었다 — 주장하는 효과가 진짜가 아니라면 같은 결과를 내야 하는 입력. 계획서에 적어 놓고 돌리지 않았다. 주 결과가 설득력 있어 보였기 때문이다. 돌려 보니 결론이 뒤집혔다.
그 뒤로 상시 규칙: 대조군 없는 결과는 결과가 아니다. 그리고 대조군은 판정을 적기 전에 돌린다. 의심이 든 뒤가 아니라.
지웠던 지문이 한 달 만에 돌아왔다
2026-08-07 · chore(scrub): remove a partner organisation's fingerprint, and count it on every push
공개 전환 전에 식별 정보를 스크럽했다. 한 달 뒤, 평범한 작업을 통해 그것들이 돌아와 있었다 — 주소 하나, 실제 페이지 식별자 하나, 그리고 아무도 안 보던 사각지대인 커밋 메시지와 PR 본문 안에.
이제 매 푸시마다 스캐너가 돈다. 얼어붙은 웹훅 페이로드가 아니라 API 에서 현재 제목·본문을 읽으므로 나중의 수정이 빠져나가지 못하고, 음성 대조군을 달고 있어서 아무것도 못 잡게 된 스캐너가 조용히 통과하는 대신 요란하게 실패한다. 허용목록은 예외가 아니라 근거를 받는다: 나중에 어떤 테스트가 여기 걸렸을 때 처방은 합성 식별자를 쓰는 것이었지 허용목록에 줄을 더하는 것이 아니었다 — 사건마다 예외가 하나씩 붙는 목록은 일정표를 가진 구멍이다.
리포에 스키마가 두 개 있었다
2026-08-09 · fix(ci): the recall job never ran migrations, so the repo had two schemas
CI 잡 하나가 베이스라인 파일로 DB 를 만들고 그 위에 마이그레이션을 적용하지 않았다. 즉 어떤 배포도 돌린 적 없는 스키마를 테스트하며 통과하고 있었다.
적재가 아무도 검색하지 않는 컬럼에 썼다
2026-08-11 · fix(nexus): the corpus never said which generation serves it, so two processes disagreed
임베딩 모델·차원·기록 대상 벡터 컬럼은 한 단위다 — 세대. 문서화된 명령을 호스트에서 돌리면 그 단위를 설정 기본값에서 해석하고, 컨테이너는 환경변수에서 해석했다. 둘이 어긋났고, 그래서 완벽하게 성공한 적재가 어떤 질의도 읽지 않는 컬럼에 벡터를 썼다. 아무것도 실패하지 않았다. 문서가 그냥 찾아지지 않았을 뿐이다.
이제 코퍼스는 세대를 DB 에 append-only 로 선언하고, 도는 프로세스가 다른 세대를 해석하면 문서를 한 건 수집하기도 전에 적재가 거부된다. 같은 변경이 하나를 더 못박았다: 청크 텍스트가 바뀌면 모든 벡터 컬럼이 무효화된다 — 예전 결정 기록이 죽었다고 적어 둔 버그가 컬럼이 하나 늘자 그대로 되살아났다.
숫자는 있었고, 아무에게도 보여지지 않았다
2026-08-11 · fix(nexus): the coverage number existed, and no one was ever shown it
인덱스 커버리지 — 각 검색 경로가 실제로 볼 수 있는 청크가 몇 개인가 — 는 처음부터 정확히 계산되고 있었다. 그것이 API 기동 로그에 적혔고, 그곳은 사람이 보지 않는다. 사람이 실제로 치는 명령은 아무것도 안 보여줬고, 51개 청크의 구멍이 하루를 지나갔다.
이 로그에서 가장 자주 반복되는 모양이고, 지금까지 네 번 따로 나타났다: 감지기는 있고 전달이 없다. 여기서 나온 규칙은, 사람이나 에이전트가 실제로 읽는 무언가가 그것을 보여주기 전까지 그 신호의 값은 0이라는 것 — 그리고 감지기만 검사하면 전달을 지워도 초록이므로 검사가 표면을 실행해야 한다는 것이다.
실패의 절반은 내 것이었다
2026-08-12 · feat(nexus): measure a second corpus, and find out half the failures are mine
두 번째 코퍼스에 평가를 돌리자 실패가 깨끗이 갈렸다: 일부는 시스템의 것이었고, 나머지는 채점기 자체의 결함이었다 — 답이 없는 문서를 가리키는 라벨, 네 번째 표현에 그대로 뚫린 기권 탐지기, 검증기가 해소하지 못하는 인용 형식.
기권 탐지기가 교훈적이다. 그것은 알려진 거절 문구의 목록이었고, 다음 실행이 목록에 없는 표현을 냈고, 그래서 정직한 거절이 환각으로 채점됐다. 관측된 문자열의 목록은 탐지기가 아니다. 구조 규칙으로 교체했다: 거절은 근거를 지목하며 부정한다 — 그 문장을 전에 본 적 있는지에 의존하지 않는 규칙이다.
채점기가 더는 시스템을 가르지 못했다
2026-08-13
두 평가 팩이 모두 천장에 닿았다. 앞선 한 주 동안 점수가 올라갔는데 — 그 상승분 전부가 계측기 수리로 추적됐다. 검색도 생성도 건드리지 않았다.
두 팩을 품질 근거가 아니라 회귀 검사로 재분류했고, 리포는 그 총점을 시스템이 얼마나 좋은지의 척도로 인용하기를 그만뒀다. 두 시스템을 가르지 못하는 척도는 그것들을 측정하고 있지 않다.
채점기가 구조적으로 볼 수 없는 결함을 통과시켰다
2026-08-18 · feat(nexus): tell the user when the evidence does not fit the question
자동 점수는 8점 만점에 7.7이었다. 배포된 팀은 쓰지 않았다. 이유를 묻자 한 사람이 답변이 석연치 않다고 했다 — 그 한 문장이 몇 주 동안 모든 자동 검사를 통과해 온 결함을 열었다.
재현: 도구 이름의 유래를 물었더니 시스템이 근거 10칸을 모두 채우고 기술 스택 표와 API 응답 구조를 길게 답했다. 환각이 아니다. 인용은 해소됐고, 그라운딩 검사도, 사실 검사도 통과했다. 가용한 어떤 측정으로도 이것을 볼 수 없었다.
원인은 융합 단계에 있었다. RRF 는 결과를 1/(k + 순위) 로 점수화하므로 순위를 담고 크기를 버린다 — 그런데 두 검색 경로는 정렬하려고 크기를 이미 계산해 놓고 반환에서 버리고 있었다. 되살리니 세 모집단이 깨끗이 갈린다:
| 벡터 거리 | 키워드 점수 | |
|---|---|---|
| 답할 수 있음 | 0.191 – 0.455 | 2.0 – 4.5 |
| 주제는 있고 답이 없음 | 0.377 – 0.470 | 0.8 – 1.2 |
| 코퍼스 밖 | 0.544 – 0.575 | 0.1 – 0.6 |
약한 근거가 답을 막지는 않는다 — 서술 계약만 바꾼다. 그래서 문턱이 틀렸을 때의 대가가 잘못 삼킨 답이 아니라 짧은 답이 된다. 문턱은 저술한 질문 17개에서 나온 가설로 기록돼 있고, 이제 요청마다 두 크기를 저장하므로 언젠가 발명이 아니라 실사용이 그 값을 정할 수 있다.
이 항목에는 정정이 붙는다: 주제는 있고 답이 없음을 대표하도록 쓴 질문 6개 중 2개가 사실은 답할 수 있는 질문이었고, 시스템은 인용을 달고 정확히 답했다. 부재 확인을 코퍼스의 어휘가 아니라 저자의 어휘로 돌린 것이다. 다시 나누니 읽기는 오히려 강해졌지만, 교훈은 이것이다 — 자기 말로 증명한 부재는 부재가 아니다.
어떤 검색 경로도 읽을 수 없는 문서
2026-08-18 · fix(nexus): keep a chunk's generation key with the document it belongs to
soft-delete 된 문서를 되살리면 청크의 “현재 세대만” 복원되고, 그 판정은 청크에 달린 키를 문서의 content hash 와 비교해서 한다. 재적재는 문서의 해시를 갱신하면서 청크의 것은 옮기지 않았다. 그래서 한 번이라도 편집된 문서는 과거에 멈춘 청크를 이고 있었고, 이후의 삭제→복원은 문서를 읽을 수 있는 청크 0건으로 다시 세웠다 — 목록에 뜨고, 개수에 세어지고, 건강하다고 보고되면서 어떤 검색 경로도 한 글자 못 읽는 상태로.
팀이 실제로 질의하는 라이브 코퍼스에 그런 문서가 하나 있었고, 같은 트리거를 기다리는 것이 여덟 개 더 있었다. 그 트리거는 사람의 명령이 아니라 예약된 재조정 작업이다. 이것을 숨긴 사각지대는 이름을 붙여 둘 값이 있다: 커버리지는 청크 단위로 계산되므로, 청크가 0건인 문서는 모집단 밖이라 100% 로 보고된다.
첫 페이지가 바뀐 검색 경로를 계속 서술하고 있었다
2026-08-21
이 사이트 맨 위의 그림은 쿼리가 세 리트리버로 분기해 RRF 로 융합된다고 말하고 있었다. 융합되는 것은 두 다리다. 그래프 조회는 따로 돌고 다양성 컷이 끝난 뒤 붙으며, 랭킹에 아무것도 기여하지 않는다.
바로 그 오류는 엿새 전에 이미 발견돼 고쳐져 있었다 — search/hybrid.py 에 앵커로 묶여 감시받는 에이전트 지시 파일에서. 고침이 공개 페이지까지 오지 않은 이유는 하나다: 홈페이지가 앵커 목록에 없었다. 검색 아키텍처에 대해 가장 많이 읽히는 서술이, 드리프트 검사가 유일하게 안 보던 것이었고 그래서 가장 오래 표류했다 — 자기가 서술하는 코드에 34개 커밋이 쌓이는 동안.
두 언어판을 모두 고치고 둘 다 앵커에 올렸다. 일반화는 불편하지만 갖고 갈 값이 있다: 검사는 등록된 것만 지킨다. 그리고 빠뜨리기 쉬운 페이지는 문서가 아니라 홍보처럼 느껴지는 페이지들인데, 그것이 정확히 사람들이 제일 많이 읽는 것이다.
기능을 꺼도 15/15 였다
2026-08-26 · feat(nexus): the answer-fact ruler measured mention, not assertion — split them
검색 지표가 못 보는 것을 측정하려고 새 채점기를 만들었다. 답변에 그 값이 실제로 들어 있는가. 15문항 중 15를 읽었다. 그다음 이 채점기가 평가하려던 검색 변경을 끄고 다시 돌렸더니, 여전히 15/15 였다.
두 사실의 원인은 하나다. 부분일치는 주장된 값과 나열된 값을 구별하지 못한다. 문제의 답변은 근거별 수치를 늘어놓은 표에 4,000 을 적어 놓고, 결론에서 “확인 전까지는 어느 수치도 단정할 수 없습니다” 라고 물러섰다. 채점기는 그것을 정답으로 셌다.
새 판은 값이 어디에 서 있는지를 본다. 하나는 선두, 첫 표나 인용이나 헤딩보다 앞에 오는 산문이고, 시스템이 자기 프롬프트에서 답을 위해 비워 둔 자리다. 다른 하나는 접속 부사가 여는 결론 세그먼트다. 그 밖은 근거를 늘어놓는 자리로 본다.
팔당 3회, 질의별 다수결, 검정 전에 잡음 폭부터:
| 변경 켬 | 끔 | 잡음 폭 | 격차 | |
|---|---|---|---|---|
| 부분일치 | 15 · 15 · 15 | 15 · 15 · 15 | 0 | 0 |
| 주장 | 14 · 13 · 14 | 9 · 9 · 10 | 1 | 5 |
격차는 잡음을 넘었다. 그래도 부호검정은 돌리지 않는다. 불일치쌍이 5인데 사전등록한 최소치가 6이다. 다섯 건 전부 한 방향이고 반대 방향은 없지만, 주장은 거기서 멈춘다. 회차를 더 돌려도 이 수는 늘지 않는다. 불일치쌍을 정하는 것은 표본 추출이 아니고 질문 세트다.
여기서 나온 규칙은 이제 무엇을 측정하기 전에 먼저 적용된다. 점수를 읽기 전에 “이 채점기가 처치를 볼 수 있는가” 를 묻는다. 1.000 에 붙은 채점기는 좋은 결과가 아니다. 없는 결과다. 같은 회차에서 부분일치의 두 번째 결함도 드러났다. 폐기된 정책의 인용 안에만 나오는 값을, 답변이 전혀 다른 결론을 내는데도 정답으로 센다.
계측기가 우리 자신의 재적재를 세고 있었다
2026-08-26 · fix(nexus): the re-ingest signal counted our own re-runs — give it a denominator
엔트로피 신호 하나는 재수집 덮어쓰기를 센다. 같은 신원 아래에서 내용이 바뀐 문서다. 승인된 아키텍처 결정이 이 수를 demand-pull 트리거로 지정했고 여러 보류 항목이 여기 걸려 있다. 무엇을 만들지가 이 숫자로 정해진다는 뜻이다.
바로 전날 그 뷰를 고쳤다. 전역 집계라 버릴 평가 테넌트가 라이브 신호를 삼키고 있었다. 전역 중복쌍 61,425 대 라이브 0. 테넌트별로 가르는 것이 그 수정이었다.
절반이었다. 라이브 수는 이벤트 53인데 그것이 닿은 문서는 18개이고, 38건이 사흘 안에 몰려 있다. 활성 문서는 126이다. 그 사흘은 적재 파이프라인을 고치며 재적재를 돌린 날이다. 이 신호는 코퍼스가 얼마나 흔들리는지가 아니라, 우리가 같은 열여덟 개를 몇 번 다시 넣었는지를 측정하고 있었다.
수정은 지표를 재정의하지 않고 분모를 더한다. 승인된 결정 문서가 이 신호를 events 로 정의했고, 그 컬럼의 뜻을 조용히 바꾸면 승인 문서와 코드가 갈린다. 53을 혼자 읽던 사람이 이제 53과 18을 함께 읽는다.
못 고치는 것은 마이그레이션 본문에 적어 뒀다. 우리 재적재와 진짜 문서 편집은 둘 다 content hash 를 바꾸므로 저장된 데이터로는 가릴 수 없다. 가리려면 쓰는 시점에 원인을 남겨야 하고, 그건 마이그레이션으로 될 일이 아니고 명세다.
불편한 것은 순서다. 그 뷰를 테넌트별로 가른 사람과 두 번째 겹을 못 본 사람이 같고, 하루 차이다. 계측기의 절반을 닦으면 나머지 절반이 깨끗해 보인다.
돈이 나가는 유일한 경로에 장부가 없었다
2026-08-26 · feat(nexus): book what the screenshot reader spends — the only path that costs money had no ledger
어느 적재 실행이 지출 0으로 보고됐다. 그 실행은 그림 39장을 공급자로 보냈다.
거짓말이 아니라 부재였다. 지출 모듈은 있었고, 한때 하루치 유료 호출을 태운 평가 스크립트 둘에 배선돼 있었고, 적재 경로에는 아무 데도 없었다. 그래서 그 수는 손으로 센 것이었고, 손계산은 루프가 죽은 페이지들을 빠뜨렸다. 하필 호출을 만든 것이 바로 그 페이지들이었다.
이제 판독은 답변 경로가 이미 자기 것을 적는 방식 그대로 장부에 오른다. 백엔드가 토큰을 돌려주고, 같은 단가표에서 한 번 값이 매겨지고, 실행 단위로 쌓이고, 끝날 때 출력된다. 셋을 일부러 갈라 놓았다. 어느 하나라도 뭉치면 안심하게 만드는 숫자가 나오기 때문이다.
- 캐시 적중은 호출이 아니다. 세면 지출이 부풀려진다
- 실패는 호출이다. 안 세면 “몇 장을 보냈나” 가 틀리고, 그것이 정확히 틀렸던 수다
- 금액 미상은 무료가 아니다. 실행이 단가 없는 모델의 이름을 말하고, 지어내지 않는다
배선하다 잠복 결함이 하나 나왔다. 새 인자를 늘 넘기면 그것을 모르는 판독기가 타입 오류를 내는데, 추출 경로는 그림 한 장의 예외를 전부 잡으므로 그 오류가 판독 실패로 삼켜진다. 모든 카운터가 정상을 보고하는 채로 그림이 조용히 안 읽히게 된다. 장부를 안 달면 호출은 이전과 글자 그대로 같게 뒀고, 그 성질을 검사가 박고 있다.
손으로 만든 숫자는 측정이 아니다. 아무도 볼 수 있는 실패 모드가 없다.
이 로그가 합쳐서 말하는 것
개선은 기법 추가가 아니라 결함 제거에서 나왔다. 검색 기법 일곱 개를 시도하고 측정했다 — 다중 홉 검색, 모델 기반 질의 재작성, 빈도 기반 확장, 코퍼스 합치기, 그 밖의 것들. 일곱 전부 측정으로 기각됐다. 이 기간의 실제 이득은 전부 망가진 것을 걷어낸 데서 나왔다: 정답 구절을 잘라내던 다양성 상한, 검색 색인을 오염시키던 추출 마커, 융합에서 버려지던 크기.
같은 모양이 계속 반복됐다: 감지기는 있고 전달이 없다. 커버리지는 계산됐고 보여지지 않았다. 문서↔코드 앵커는 몇 주 동안 쓰이기만 하고 읽는 곳이 없었다. 거절 사유는 뷰 하나만 볼 수 있는 곳에 기록됐다. 지금의 규칙은 감지기만 검사해서는 부족하다는 것 — 검사가 사람이 실제로 보는 표면을 실행해야 하고, 빨간불이 되는지 일부러 한 번 깨뜨려 확인해야 한다.
시스템보다 계측기가 더 자주 틀렸다. 이건 계측기에 대한 불평이 아니라, 여기서 계측기를 일급 산출물로 취급하는 이유다 — 서명된 라벨, 사전등록된 판정 규칙, 그리고 점수를 본 뒤에 채점기를 고치는 것에 대한 상시 금지와 함께. 가장 날카로운 사례가 가장 최근 것이다: 만점을 읽으면서 자기가 측정하려던 변경을 못 보던 채점기, 그리고 다른 오염을 고친 바로 다음 날 우리 자신의 재적재를 세고 있던 엔트로피 신호. 둘 다 주장을 그것이 서술하는 대상에 대 보다가 나왔다 — 값싸고, 이제는 뭔가 이상하다고 느낄 때가 아니라 정해진 주기로 한다.
이 페이지는 기록이지 현황판이 아니다. 지금 열려 있는 것은 OPEN.md 를 보라 — 미결을 세어서, 그것이 늘고 있는지 줄고 있는지 알 수 있게 해 둔 파일이다.
하니스가 아무도 안 지나는 경로를 측정하고 있었다
2026-08-29 · fix(nexus): the harness was measuring a path no user takes
라벨 둘이 실험 스크립트에서 5/5, 4/5 로 통과했다. 같은 라벨을 평가 하니스로 돌리자 통째로 실패했다.
같은 코퍼스, 같은 질문, 같은 채점 규칙이었다. 다른 것은 근거를 만드는 방법 하나였다 — 실험은 답변 경로가 쓰는 함수를 불렀고, 하니스는 자기가 직접 조립했다. 그래서 하니스에는 그 주에 들어간 보강 둘이 빠져 있었다. 하니스가 내는 모든 수가 아무도 지나지 않는 시스템을 서술하고 있었다.
같은 함수로 돌리자 두 라벨이 통과로 돌아왔다. 그게 증거다.
이 리포는 같은 모양을 이미 여러 번 적었다 — 등록이 실행 가드 뒤에 있던 것, 사본이 회귀 검사 밖이던 것, 배선이 세 경로 중 하나에만 붙던 것. 새로 배운 것은 그 목록에 측정 도구 자신이 들어간다는 점이다. 측정이 프로덕션과 다른 길을 가면, 그 측정은 초록인 채로 아무것도 지키지 않는다.
계측기가 없는 병을 만들었고, 나는 그 병을 고치러 갔다
2026-08-29 · fix(nexus): a value has surface forms, and one missing form invented a defect
라벨에 기대값을 한 가지 표기로만 적었다. 시스템은 다른 표기로 옳게 답했다. 채점기는 세 번 다 실패로 찍었다.
여기까지는 흔한 라벨 결함이다. 값이 나간 곳은 그다음이다. 나는 그 실패를 시스템 결함으로 읽었다 — “근거를 쥐고도 낡은 쪽을 고른다”. 그 진단에 맞는 처방을 설계하고, 프롬프트 조항을 만들고, 실행군마다 다섯 번씩 돌려 측정했다. 사전에 박아 둔 채택 규칙이 그것을 기각했다. 고칠 병이 없었기 때문에 당연한 결과였다.
실패한 답변을 한 번 읽자 3분 만에 끝났다. 그전까지 나는 점수만 보고 있었다.
그래서 규칙 하나가 늘었다: 실패를 진단하기 전에 실패한 답변을 읽는다. 점수는 어느 값이 빠졌는지까지만 말하고, 그 값이 정말 빠졌는지는 말하지 않는다. 표기를 넓혀 다시 측정하니 그 보강의 실제 효과는 처음 보고한 3/5 이 아니라 0/5 → 5/5 였다.
정직하게 만들려던 수정이 실물 모순을 덮었다
2026-08-29 · eval: labels say what they are measured against, and a "fix" that hid a conflict
문서 코퍼스로 채점하면서 “제품이 실제로 어떻게 동작하나” 를 묻는 문장을 쓰고 있었다. 그러면 정확한 인용을 단, 현실과 다른 답이 만점을 받는다. 그래서 라벨마다 무엇을 권위로 삼는지 적고, 질문 문장도 “문서 기준으로” 라고 밝히도록 고쳤다.
그 수정을 측정에 걸었더니 이렇게 나왔다:
| 갈린 문서가 근거에 | 답변 | |
|---|---|---|
| 원래 문구 | 들어옴 | 두 값을 다 내고 불일치를 표시 (2/2) |
| 권위 접두 | 빠짐 | 한 값만 단정 (2/2) |
접두어가 검색을 좁혀 모순을 담은 문서가 후보에서 통째로 빠졌다. 답변은 받은 근거에 충실했을 뿐이다. 근거 도달은 회차와 무관한 결정론이므로 잡음이 아니다.
되돌렸다. 권위는 메타데이터로만 적는다. 라벨의 질문 문구는 자유 변수가 아니다 — 건드리면 검색이 바뀐다. 정직하게 만들려던 문구가 이 도구가 잡으라고 만들어진 바로 그것을 가릴 뻔했다.
만들어 놓고, 읽는 쪽을 안 만들었다
“닉네임 길이 상한이 얼마인가” 를 묻자 문서 둘이 다른 값을 말했다. 코드를 열어 보니 서버는 그 값을 아예 검증하지 않았고 컬럼만 20자로 잘려 있었다. 문서 둘 중 하나를 고르는 문제가 아니었다.
사용자가 정리해 준 요구는 판정이 아니라 병치였다 — “구현이 문서를 어긴 것일 수도 있고 문서가 갱신되지 않은 것일 수도 있다. 그러니 둘 다 알려줘야 한다.” 그 기능이 있는지 확인했더니 네 층 중 셋이 끊겨 있었다.
| 층 | 상태 |
|---|---|
| 코드 값 해석기 | 있다 |
| 코드 트리 마운트 | 있다 |
| 등록된 개념 | 0행 — 시드 파일은 예시 앱용이었다 |
| 답변 경로 연결 | 없다 — 전용 CLI 하나에서만 닿았다 |
고치면서 셋이 더 나왔다. 해석기가 읽는 모양이 실물과 달랐다 — static final 상수만 읽는데 실제 Java 에서 길이 상한은 @Size(max = 100) 과 @Column(length = 20) 에 산다. 대 보려던 값에 하나도 닿지 못하는 상태였다. 비용을 틀린 곳에서 찾았다 — 첫 해석 55.9초의 범인이 새 파싱인 줄 알고 사전 거르기를 넣었는데 개선이 0 이었다. 나눠 측정하니 목록 만들기 50.70초 · 읽기 5.28초 · 파싱 0.18초로, 바인드 마운트를 훑는 비용이었다. 시드가 죽은 claim 을 살아 있는 것처럼 보여줬다 — 11건 적재 만 찍었고, 코드에 안 붙어도 행은 들어간다.
결과는 이렇게 나온다.
**50자** 제한입니다 — 단, 코드와 불일치가 있어 함께 안내합니다.⚠ 서버 요청 검증 코드의 상한은 500자로, 문서의 50자와 10배 차이가 납니다.어느 쪽이 맞는지는 제공된 근거만으로 판정할 수 없습니다.세 값이 갈렸다 — 파티 이름 30 대 100 · 파티룸 소개 50 대 500 · 닉네임 12 대 20. 시스템은 어느 쪽도 고르지 않는다. 층이 달라 둘 다 맞을 수도 있어서다. 제품 규칙 30, 서버 방어선 100, 컬럼 255 는 이 코드베이스에 실제로 있는 모양이다.
정정 (2026-08-31). 위에서 나는 닉네임에 “서버 검증 없음” 이라고 적었다. 거짓이었다. 사용자가 “제한이 없다고? 테스트 코드에도?” 라고 되물어 확인해 보니 검증은 네 겹으로 있었다 — 사용자 경로 UpdateMyBioRequest @Size(max = 20), 도메인 값 객체 Nickname 이 20 초과에 예외, 컬럼 varchar(20), 그리고 21자가 실패하는 테스트까지. 코드는 일관되게 20 이다.
내가 본 것은 관리자·봇 생성 경로뿐이었다. 그리고 사용자 경로를 놓친 데는 기계적인 이유가 있었다 — 필드 선언을 찾는 정규식이 private|protected|public 으로 시작할 것을 요구했는데, 그 클래스는 Lombok 을 써서 제어자를 안 붙인다.
@Getterpublic class UpdateMyBioRequest { @Size(max = 20, message = "닉네임은 20자를 초과할 수 없습니다") String nickname; // ← 제어자가 없다그래서 없는 것을 없다고 읽은 게 아니라, 못 읽은 것을 없다고 읽었다. 두 상태를 가르지 못하는 도구가 확신하는 문장을 내면 그게 나온다. 지금은 타입 토큰만 요구하고 제어자는 요구하지 않으며, 실물 모양이 검사로 박혀 있다. 심은 claim 이 4/7 에서 6/7 로 붙었다.
⛔ 남은 한계 하나를 같이 적는다. 도메인 값 객체의 if (value.length() > 20) throw … 는 상수도 어노테이션도 아니라 지금도 못 읽는다. 위 20이 세 겹인 것은 사람이 확인한 것이지 도구가 낸 것이 아니다.
전수 확인 (2026-08-31). 정정한 김에 문서값 11개를 전부 코드에 대 봤다. 일치 4 · 불일치 6 · 코드 자리를 못 찾은 것 1 — 내가 앞서 “셋”이라고 적은 것은 전수가 아니었다. 그리고 이력이 단서를 준다: 불일치 넷(파티 이름 100 · 파티룸 소개 500 · 디제잉 최소 1분 · 플레이리스트 이름 100)이 한 커밋에서 왔다 — DTO 18개에 검증을 한꺼번에 붙인 일괄 작업이다. 필드별 제품 결정이 아니라 일괄 기본값으로 보인다는 뜻이고, 그 판단은 값만 봐서는 안 나온다. ⛔ 못 찾은 하나는 “없다”가 아니라 “못 찾았다” 로 적었다 — 방금 그 둘을 헷갈려서 틀린 참이다.
이력이 답을 줬다 (2026-08-31). 소유자가 “파티 이름 100, 소개 500은 말이 안 된다” 며 변경 이력을 물었다. 파 보니 한 번도 바뀐 적이 없었다. 두 파일은 2024년부터 있었고, 크기 검증이 붙은 것은 2026-03-09 한 번뿐이다 — 그전에는 검증이 아예 없었다. 그러니 100·500은 낡은 규칙의 잔재가 아니라 검증을 처음 붙이던 날 고른 기본값이다. 값만 봐서는 둘을 못 가른다.
그리고 파는 김에 하나가 더 나왔다. 소개 검증은 @Size(max = 500) 인데 컬럼은 varchar(255) 이고, 엔티티에 재정의가 없으며 ddl-auto: validate + Flyway 다. 256~500자 소개는 검증을 통과하고 저장에서 실패한다 — 문서 불일치가 아니라 깨진 것이다.
⛔ 그리고 앞서 “코드 자리를 못 찾았다” 고 적은 공지 제한은 있었다. @Size(max = PartyroomData.MAX_NOTICE_CONTENT_LENGTH) 로 상수를 참조하고 있어서 내 검색이 못 봤다. ‘못 찾았다’ 로 적어 둔 것이 이번엔 값을 했다.
닉네임 20의 기원 (2026-08-31). 소유자가 닉네임도 이력을 물었다. 끝까지 파니 이렇게 나온다.
| 때 | 무엇 |
|---|---|
| 2023-05 ~ 09 | 컬럼 varchar(255) — 제한이 없다 |
| 2023-07-30 · 08-31 | 문서 개정 두 건, 닉네임 제한 관련 (문서가 12자 · 8·16 을 말하는 시기) |
| 2024-06-12 | @Column(length = 20) 이 처음 등장 — 커밋 제목은 “Add temporary users in local profile” |
| 2026-02-15 | 요청 검증 @Size(max = 20) 추가 |
| 2026-02-20 | 도메인 Nickname 이 20 초과에 예외 |
| 2026-04-19 | Flyway baseline 이 varchar(20) 을 고정 |
20은 한 번도 제품 결정으로 정해진 적이 없다. 2024년에 임시 사용자 기능을 넣다 붙은 컬럼 길이이고, 2026년의 두 번은 그것을 복사했다. 그리고 코드에 12나 8·16 이 있었던 적은 없다.
같은 2026-02-15 커밋이 소개글은 “to match frontend limit” 이라며 30→50 으로 올렸다. 한 커밋 안에서 한 필드는 제품 규칙을 따라갔고 다른 필드는 컬럼을 따라갔다. 값만 보면 둘 다 그냥 숫자다.
전수는 절반이었다 (2026-08-31). 소유자가 “라벨 18개도 전수조사를 다시 했었나” 라고 물었다. 안 했다. 코드에 대 본 것은 값 라벨 11개뿐이고, 권한 4개와 설계 3개는 손도 안 댔다. 그러고도 나는 “전수” 라고 적었다.
남은 일곱을 마저 대 봤다. 권한 넷은 전부 일치한다 — 파티룸 폐쇄는 방장 본인만(validateHost), 공지는 CM 이상(isBelowGrade(COMMUNITY_MANAGER)), 역할 부여와 밴은 Mod 이상(isBelowGrade(MODERATOR)). 설계 셋은 애초에 코드 확인을 달고 쓴 라벨이고 재확인해도 성립한다.
값은 여섯이 갈리는데 권한은 하나도 안 갈린다. 그럴 만한 이유가 있어 보인다 — 권한은 틀리면 바로 사고가 나는 자리고, 길이 제한은 아무도 안 보는 사이 컬럼 폭으로 흘러간다.
⛔ 그리고 하나 더 나왔다. “라벨 18개 서명” 은 내가 매 보고에서 말해 온 미결인데 미결 목록에는 없었다. 말로만 굴리고 기록에는 안 올려 둔 것이다.
정답을 안 대 봤다 (2026-08-31). 소유자가 세 번 물어서야 알아들었다. 나는 라벨을 코드에 대 보는 일만 하고, 라벨의 정답 자체가 코퍼스와 맞는지는 한 번도 확인하지 않았다. 그 정답은 내가 쓴 것이다.
대 보니 열여덟 중 이렇게 나온다.
| 확인됨 | 12 |
| 틀린 라벨 | 1 |
| 근거가 갈림 | 1 |
| 청크 하나에만 걸림 | 4 |
B-4 가 틀렸다. 매트릭스를 행 단위로 읽으면 두 행이 다르다.
킥 (재입장 가능) | O | O | O | X | X | ← Mod 가능밴 (재입장 불가능) | O | O | X | X | X | ← Mod 불가나는 밴을 물어 놓고 킥의 값을 정답으로 적었다. 그리고 이 정정이 결함 하나를 연다 — 코드는 두 행을 안 가른다. 게이트 하나(isBelowGrade(MODERATOR))가 일회성 퇴장과 영구 퇴장을 함께 받는다. 즉 코드에서는 Mod 가 영구 밴을 걸 수 있는데 문서는 Admin·CM 만이라고 한다.
⛔ 바로 앞 회차에 나는 “권한 넷은 전부 일치” 라고 적었다. 그건 틀린 라벨로 대 본 결과였다. 라벨이 틀리면 대조도 같이 틀리고, 틀린 방향은 언제나 “문제 없음” 쪽이다.
그리고 A-2 는 같은 문서에서 50자 와 60자 가 둘 다 읽힌다. 넷은 청크 하나에만 걸려 있다 — 틀렸다는 뜻이 아니라 대조군이 없다는 뜻이다.
안전장치가 필요한 자리에만 없다 (2026-08-31). 소유자가 “정원 200명 라벨은 왜 그대로냐” 고 물었다. 라벨은 사전 등록 규칙대로 그대로 두는 것이 맞다 — 이 라벨들은 “문서를 정확히 읽는가” 만 측정하고 “제품이 실제로 그런가” 는 측정하지 않으며, 어긋남은 점수가 아니라 보고 대상이라고 규칙에 박혀 있다.
그런데 그 질문을 라이브로 눌러 보니 더 나쁜 것이 나왔다.
"파티룸 정원은 몇 명이야?" → 200명 만 답한다 (소유자 결정: 50 이 맞다)"디제잉 최소 몇 분?" → 문서 3분 · 코드 1분 둘 다 답한다차이는 claim 을 심었느냐가 아니라 값이 어떤 모양으로 코드에 있느냐다. 디제잉 최솟값은 @Min(value = 1) 이라 읽히고, 정원은 if (activeCrewCount > 49) 라 안 읽힌다. 즉 문서가 틀린 것이 확정된 값에서 바로 그 안전장치가 빠진다.
같은 처지가 셋 더 있다 — 자동 폐쇄 30일(메서드 인자), 폐쇄 배치 3시(메서드 어노테이션), 닉네임 도메인 가드. 지금은 문서와 코드가 맞아서 안 보일 뿐이다.
⛔ 그리고 소유자가 정원을 50으로 결정한 그 자리에서 내가 라벨 셋(A-5·A-6·B-4)의 처지를 짚었어야 했는데 안 했다. 규칙대로 둔 것과 아무 말도 안 한 것은 다르다.
서명할 게 거의 없었다 (2026-08-31). 소유자가 물었다: “근거 문서명을 네가 밑에 적어놨잖아. 그럼 그게 맞겠지. 이건 문서를 정확히 읽는가만 측정하는 거라며.”
세어 보니 그 말이 맞았다. 라벨 18개의 근거 문구 중 15개는 코퍼스 원문 그대로다 — 기계가 대조할 수 있고 사람이 볼 이유가 없다. 사람 판단이 실제로 필요한 자리는 셋뿐이었다: 코퍼스가 자기끼리 갈리는 값 하나, 어느 문서가 기준인가 하나, 그리고 내가 O/X 칸을 읽어 옮긴 권한 매트릭스.
매트릭스가 유일하게 인용이 아닌 자리이고, 결함 넷 중 셋이 거기서 나왔다.
| B-4 | 밴을 물어 놓고 킥의 행을 읽었다 |
| B-3 | 질문이 중의적이다 — 부여하는 쪽인지 받는 쪽인지 문장이 안 정한다. 그리고 근거 행은 클러버 권한 부여 인데 질문은 역할 부여 일반이었다 |
| A-11 | 문서는 소개 라고 부르는데 나는 상태 메시지 라고 물었다 |
B-3 의 중의성은 소유자가 잡았다 — “수여자부터 판단하는 게 맞지.” 코드가 일반 규칙이라 답은 어차피 같았고, 그래서 안 고치고 넘어갈 수도 있었다. 그러면 다음에 코드와 문서가 갈릴 때 무엇을 물었는지 아무도 모른다.
⛔ 세 결함 다 서명 전에 나왔다. 점수가 아직 없어서 라벨을 고쳐도 조작이 아니다 — 규칙이 점수를 서명 뒤로 미뤄 둔 이유가 이것이다. 다만 그 규칙이 지켜 준 것은 순서이고, 결함을 찾아낸 것은 사람의 반문 세 번이었다.
서명 직후 첫 점수가 채점기를 고발했다 (2026-08-31). 소유자가 서명하자마자 18개를 돌렸다. 정책 15개에서 A-10 이 언급=실패 · 주장=통과 로 찍혔다. 2판은 1판의 부분집합이라 그 조합은 성립할 수 없다.
결함은 둘이었고 둘 다 내 코드다. type: conflict 갈래가 ok 를 다시 계산하지 않아 모순 라벨은 어떤 답으로도 1판을 통과할 수 없었고, mentioned 는 expect 가 없는 라벨에서 무조건 False 였다. 그 모듈에는 검사가 하나도 없었다.
고치고 다시 돌렸다.
| 첫 회차 | 고친 뒤 | |
|---|---|---|
| 1판 언급 | 14/15 | 15/15 |
| 2판 주장 | 12/15 | 13/15 |
첫 회차의 14/15 는 폐기했다. 한 번 찍힌 수는 인용된다 — 이 리포가 그렇게 라벨 문제를 물려받았고, 그래서 서명 전에는 분수를 안 내도록 만들어 뒀다. 그 장치가 이번엔 서명 직후를 못 막았다.
그리고 남은 실패가 둘 다 결함을 하나씩 물고 있었다.
B-4 — 답변이 Mod 라고 답하며 내가 안 본 표를 인용했다. 권한 매트릭스가 두 개고 밴 행이 서로 다르다: 한 문서는 Admin·CM, 다른 문서는 Admin·CM·Mod. 코드는 Mod 다. 나는 표 하나만 보고 라벨을 Mod→CM 으로 “고쳤”던 것이고, 닉네임 때와 똑같은 실수다 — 한 곳을 보고 단정했다. 두 번째다.
A-3·A-5 — 1판은 통과하고 2판만 떨어진다. 답이 틀려서가 아니라 문서 값과 코드 값을 나란히 내면서 결론 자리에 “둘이 다르다”를 적기 때문이다. 2판은 문서 값이 선두나 결론에 서야 통과하도록 만들었고, 그건 코드 값이 붙기 전의 답변 모양을 전제한 규칙이다. 답이 좋아지자 점수가 떨어졌다.
⛔ 그 규칙은 지금 안 고친다. 점수를 보고 채점 규칙을 고치면 그건 조작이다. 다음 회차 전에 사전 등록으로 바꾼다.
공개 목록에 남의 회사 값이 쌓이고 있었다 (2026-08-31). 소유자가 짚었다 — “내가 할 일은 메모리에 저장해둬. 이 제품을 위해서 처리해야 할 일은 아니잖아.”
맞는 말이었고, 이유가 하나 더 있었다. OPEN.md 는 khala 의 미결 목록이고 공개 리포에 있다. 그런데 지난 며칠 동안 내가 거기 쌓은 것은 팀 제품의 화면 이름, 필드 이름, 길이 제한, 권한표 행이었다. khala 가 찾아낸 것은 맞지만 khala 의 할 일은 하나도 아니었다.
여섯 줄을 운영자 메모(리포 밖)로 옮기고, 자리에는 “여기 없다, 다시 옮기지 마라” 한 줄만 남겼다.
그리고 그 한 줄을 쓰다가 지문 검사가 나를 잡았다. 옮겨 간 메모 파일 이름에 조직명을 적었고, fingerprint_scan.py 가 OPEN.md:89 — 파트너 조직명, 이것은 공개된다 로 막았다. 이 검사는 2026-08-07 에 지문이 한 달 만에 재유입돼서 만든 것이다. 오늘은 경계를 정리하는 커밋 자체에서 걸렸다.
⛔ 이 리포의 결함 목록은 khala 것만 담는다. 남의 조직 사실은 값 하나하나가 지문이다.
만들려던 것 둘이 이미 있었다 (2026-08-31). 컷오버 SPEC 이 선행 조건 넷에 막혀 있었다. 둘은 소유자 서명이었고, 나머지 둘은 내가 만들어야 할 것으로 적어 뒀다 — 조각별 등급 판정과 새 수명주기 사유(그리고 그것을 처분할 ADR 하나).
만들기 전에 측정하고 찾아봤다. 둘 다 필요가 없었다.
등급 — 두 테넌트의 등급 분포를 대 보니 값이 INTERNAL·RESTRICTED 둘뿐이고 한쪽에만 있는 값이 없다. 그리고 RESTRICTED 네 건을 열어 보니 서로 사본이다. 즉 이 컷오버는 누구의 노출도 바꾸지 않는다. 조각별 판정은 어휘가 실제로 갈릴 때 — 두 번째 조직이 붙을 때 — 필요하다.
제거 수단 — resource_status enum 에 soft_deleted 가 이미 있고, hide_document(= soft_delete + hold=true)와 restore_document 가 이미 있다. 네 요구(검색에서 빠질 것·행은 남을 것·역연산이 있을 것·이유가 남을 것)를 그대로 만족한다. 덤으로 재조정이 hold=true 를 안 되살린다 — 사본이 조용히 돌아오는 경로 하나가 이미 막혀 있었다.
⛔ 나는 새 프리미티브 둘과 ADR 하나를 만들려 했고, 그 전에 찾아보지 않았다. 비평이 “자리가 아니다” 라고 밀어낸 덕에 찾아봤고, 자리가 아닌 정도가 아니라 이미 있었다.
이 로그에 이미 적힌 문장과 같은 자리다 — “RAG 검색 난제는 대개 이미 풀려 있다. 리포 내부 문서부터 뒤져라.” 그때는 남의 논문이었고 이번엔 내 리포의 함수였다.
내가 박은 규칙이 나를 멈춰 세웠다 (2026-08-31). 컷오버 전에 기준선을 다섯 번 돌렸다. 사전 등록 규칙은 이랬다 — 회차 간에 한 번이라도 흔들린 라벨은 판정에서 빼고, 잔존이 12개 미만이면 부착을 보류한다.
라벨 18 → 흔들리지 않음 11 · 흔들림 711 < 12 → 보류규칙은 점수를 보기 전에 썼고, 그것이 발화했다. 사본은 안 내렸다 — 공백 구간에 들어가지도 않았다.
흔들림의 정체가 뚜렷하다. 일곱 중 다섯은 2판만 흔들리고 1판은 고정 통과다. 답변이 회차마다 문서 값으로 시작하기도 하고 “문서와 코드가 다르다” 로 시작하기도 하는데, 2판은 값이 선두나 결론 자리에 서야 통과시킨다. 그 규칙은 답변에 코드 값이 붙기 전의 모양을 전제로 쓴 것이다. 답이 좋아진 것이 계측기를 흔든다.
⛔ 그리고 여기가 미끄러지기 쉬운 자리다. 규칙이 나를 막은 것을 보고 그 규칙을 고치면 조작이다. 고칠 근거는 이미 있다 — 이 드리프트는 오늘 아침 A28 로 등록됐고, 개정은 “다음 회차 전에 사전 등록으로” 라고 적혀 있었다. 그래도 순서를 지킨다: 개정을 먼저 등록하고, 그다음 T0 를 다시 돌린다. 지금 상태에서 12를 11로 낮추는 것은 사전 등록을 장식으로 만드는 일이다.
컷오버를 완주했다가 되돌렸다 (2026-08-31). 규칙이 두 번 발화했다.
| 단계 | 결과 |
|---|---|
| T0b 기준선 5회 | 안정 라벨 14 (≥12, 통과) |
| 사본 122건 내림 | soft_deleted + hold=true, 활성 청크 0 |
| T1 5회 | 안정 14 중 1개만 변함 — 설계 라벨 하나가 떨어졌다. 술어가 사본을 잡았다는 증거이고, 정책 13개는 그대로다 |
| 범위 부착 | 슬랙이 사본 없이 정본을 읽는 것을 라이브로 확인 |
| T2b 5회 | A-9 가 T0b 에서 5/5 고정이었는데 2판이 갈렸다 → 되돌림 |
되돌림은 한 줄이었다. 사본도 설정도 원상복구했고 라이브로 확인했다.
도중에 나온 결함 셋
① 모델 기본값이 계약을 삼켰다. AnswerRequest.tenant 의 기본값이 "default" 라, 봇이 안 보내도 채워진다. 그것을 “요청했다” 로 읽으면 “안 주면 범위 전체” 가 영원히 발화하지 않는다. 자물쇠를 열고 설정을 넣고 재기동까지 했는데 답변이 안 바뀌어서야 드러났다. 단위 검사는 요청을 손으로 만드니 필드가 항상 설정돼 있고, 프로덕션도 항상 설정돼 있는데 이유가 정반대다.
② 하니스가 다른 코퍼스에 묻고 있었다. 사본을 내리자 설계 라벨이 떨어졌고 규칙대로면 되돌리기였다. 그런데 같은 질문이 라이브에서는 정답을 냈다 — 하니스는 테넌트 문자열 하나를 검색에 그대로 넘기고 라이브는 읽기 범위를 해소한다. 네 줄 아래 주석에 사흘 전 같은 실수가 적혀 있었다.
③ 계측기가 답변 개선에 흔들렸다. 2판은 값이 선두나 결론에 서야 통과시키는데, 답변이 문서 값과 코드 값을 함께 내면서 확정 문장이 절 안으로 밀렸다. 개정은 사전 등록으로 했고, 구현하자마자 이 리포에 이미 있던 대조군이 개정을 좁혔다.
남는 것
A-9 가 왜 흔들렸는지는 아직 모른다. 설계 코퍼스가 근거에 섞이면서 답변 구성이 바뀐 것일 수도, 그 라벨이 원래 경계에 있었던 것일 수도 있다. 원인을 밝히기 전에는 다시 켜지 않는다.
⭐ 이 회차에서 값진 것은 컷오버가 아니라 막힌 자리 셋이다. 셋 다 “돌긴 도는데 아무도 안 지나는 경로를 측정하고 있었다” 는 같은 얼굴을 하고 있었다.
되돌린 것은 오경보였다 (2026-08-31 저녁). 컷오버를 규칙대로 되돌린 뒤, 되돌린 상태 — T0b 와 같은 조건 — 에서 문제의 라벨을 여덟 번 더 돌렸다.
1판: 8/8 값은 매번 나온다2판: 4/8 확정 위치가 반반이다그 라벨은 원래 흔들리고 있었다. T0b 의 5/5 는 운이었고, 컷오버가 회귀를 만든 것이 아니다.
계산해 보면 당연하다. 통과율이 0.5 인 라벨이 n회에서 균일하게 보일 확률은 2 × 0.5ⁿ 이다.
| n | 균일하게 보일 확률 | 라벨 18개 중 기대 오분류 |
|---|---|---|
| 3 | 25.00% | 4.50 |
| 5 | 6.25% | 1.12 |
| 10 | 0.20% | 0.04 |
n=5 는 오분류를 하나 기대하는 설계였고 정확히 하나 났다. 비평이 “회차 수와 추정 방식을 정하고 탐지 가능한 최소 변화량을 적어라” 고 했는데, 나는 3을 5로 올리기만 하고 검정력을 계산하지 않았다. 숫자를 미리 적었다는 사실이 그 숫자가 옳다는 뜻은 아니다.
흔들림의 정체는 채점기가 아니라 답변 형식의 비결정성이었다.
통과: **문서(설계) 기준: 20자** ← 절 머리에 확정실패: | 문서 (화면 정책) | 20자 | ← 표 행에만 있다표 행을 주장으로 안 세는 것은 설계대로다. 둘 다 좋은 답인데 형식이 갈려서 라벨이 경계에 선다. ⛔ 답변 형식을 계측기에 맞추지 않는다 — 그건 거꾸로다.
개정은 사전 등록했다: 안정성 분류를 10회로 한 번 하고 시점별 비교는 5회로 두되 분류된 라벨만 본다. 분류는 라벨과 시스템의 성질이지 시점의 성질이 아니다.
앞 항목의 원인 규명을 철회한다 (2026-08-31 저녁). “그 라벨은 원래 흔들리고 있었다” 고 적었는데, 그 결론은 라벨 하나만 골라 돌린 실행에서 나왔다. 전체를 도는 분류 10회에서는 10/10 으로 고정이다.
| 실행 방식 | 2판 |
|---|---|
| 그 라벨만 ×8 | 4/8 (두 번 재현) |
| 전체 15개 ×10 | 10/10 |
차이가 재현된다. 원인은 아직 모른다 — 하니스의 필터는 질의 목록만 거르고, 키리스 브리지는 세션을 안 남긴다. 남은 후보는 벡터 다리가 ANN 이라 후보 집합이 프로세스 상태에 흔들리는 것 정도인데 확인 안 됐다.
무엇이 살아남는지 갈라 적는다.
- ⛔ “그 라벨이 경계에 있다” 는 철회한다. 부분 실행에서만 본 것이다.
- ✅ 검정력 산수(
2×0.5ⁿ, n=5 면 18개 중 기대 오분류 1.12)는 그대로다. 그 라벨과 무관하다. - ✅ 분류와 비교가 둘 다 전체 실행이므로 이번 측정은 내부적으로 일관된다.
⚠ 그리고 규칙 하나가 늘었다 — 부분 실행으로 얻은 수를 전체 실행 조건에 인용하지 않는다. 진단할 때 한 라벨만 돌리는 것은 빠르고 편한데, 그렇게 얻은 수는 다른 조건의 것이다. 오늘 나는 그 수로 공개 기록에 원인을 적었다.