Captain's Log
Observatory··약 3분

상수예측이라는 수평선 — AIB10, 6주의 침묵과 3주 85커밋

커밋 0건의 6주 뒤 3주간 85커밋이 쌓였다. 감성 모델이 처음으로 상수예측을 넘었고(향상 +0.286), 보내지 않은 메일을 보냈다고 말하던 화면을 고쳤고, 비어 있다시피 한 운영 DB를 전환 실패가 아니라 유입 부재로 판정했다.

UnderwayMLDataRelease

항해 기록

7/4 ~ 8/18 — 커밋 0건. 확장 v1.0.0을 스토어에 올린 뒤 배는 6주간 항구에 묶여 있었다. 8/19에 돌아와 3주 동안 85커밋, PR #2~#20을 밀었다. 첫 작업은 기능이 아니라 CI였다 — 테스트 101개가 있는데 PR에서 아무도 돌려주지 않고 있었다. 8/22에는 전체 감사로 XSS 차단·CSP 강화를 묶어 확장 v1.1.0을 냈다.

감성 모델 — 상수예측을 넘기까지. 8/21의 측정은 굴욕이었다: 유튜브 정확도 0.233(3분류 무작위 0.333), 예측이 긍정 92/부정 15/중립 3으로 한 클래스에 붕괴. 원인은 도메인 불일치가 아니라 어휘 커버리지였다 — 실제 제목 단어의 65%가 학습 어휘에 없었고, "금리가/금리는/금리를"이 전부 다른 단어였다. 단어 n-gram을 문자 2-4gram으로 바꿨다. 데이터셋이 266건에 멈춰 있던 이유도 이때 드러났다 — 자동 재학습 워크플로의 시크릿이 미등록이라 8/9부터 5회 연속 실패했는데 아무도 몰랐다.

9/8 하루에 네 방이 이어졌다. 라벨 데이터 7배 증량(266→1,966건)으로 처음으로 상수예측을 넘었고(+0.151), 사실 전달형 키워드로 중립 표본을 늘려(총 5,311건) 중립 f1을 0.215에서 0.480으로 끌어올렸고, 전체 5,311건을 현재 교사로 재라벨링해 574건을 바꿨다(+0.061 — 데이터 양이 지배적이고 라벨 일관성은 부차적이라는 결론). 마지막으로 자동 재학습의 품질 가드를 val_accuracy에서 상수예측 대비 향상(lift) 기준으로 교체했다 — 직전에 유튜브 정확도 0.600이 기준을 통과할 뻔했는데, 표본의 부정 비율이 0.589라 그 모델은 '항상 부정'과 사실상 같았다. 현재 성적: real_holdout 878건, 정확도 0.656, lift +0.286.

사고도 셋 있었다. gemini-2.5-flash가 하루아침에 404가 되며 Gemini 기능이 통째로 멎었고(모델 ID가 5곳에 흩어져 있던 것을 한 곳으로 모았다), 재시도 없는 429로 라벨 2,579건 중 34%가 유실됐고, Gemini 장애 시 로컬 폴백이 전 워치에 오알림을 쏘는 구조(같은 제목 118건에 Gemini 부정 23% vs 로컬 55%)를 발견해 같은 라벨 출처끼리만 비교하도록 바꿨다.

9/7 — 메일이 정직해진 날. 가장 아픈 사고는 코드가 아니라 문장이었다. SES가 샌드박스 상태라 mailer.send()는 .eml 파일만 쓰고 정상 반환했고, 화면은 "로그인 링크를 보냈습니다"라고 말했다 — 보내지 않았는데. 확장의 첫 사용자들이 생긴 직후라 유입이 전부 이 지점에서 막혔다. 응답에 delivery 필드를 넣어 사실만 말하게 하고, 반송·불만을 SNS 웹훅으로 받아 수신을 끄는 파이프라인(테스트 16건)을 깔았다. 본문 스크래핑도 이날 걷어냈다 — 제목 기반 요약 전환으로 검색 1건 원가가 8.1원에서 1원 미만이 됐다.

공급만 보던 서비스가 수요를 본다. Watch의 지표는 전부 공급(기사 수·논조)이었다. 네이버 데이터랩 검색량으로 수요 축을 더했고, 언급량 단기 예측을 만들되 사전 등록(pre-registration) 으로 잠갔다 — 예측을 append-only로 기록하고 대상일이 지나면 자동 채점한다. 첫 성적표의 coverage80은 0.30이고, 나쁜 값이 그대로 나가게 설계했다. 알림은 텔레그램·웹훅으로 다변화했다(웹훅은 SSRF 3중 방어).

규제 경계가 설계에 박힌 기능. 기사에 등장한 상장사 표시는 KRX 상장법인 2,759건과 정확일치로만 대조한다(부분일치는 "한화"가 한화솔루션·한화에어로스페이스를 같이 무는 문제). 시세·등락률·전망은 표시하지 않고, 유료 플랜 전용화를 금지로 못박았다 — 자본시장법의 유사투자자문업 판단 기준이 "투자조언과 관련한 직접적 대가 수령"이라, plans.py 한 줄이 규제 지위를 바꾼다.

확장 1.0.0 → 1.2.0. 1.1.1(검색→Watch 진입점)을 거쳐 9/9 확장 로그인을 실은 1.2.0을 심사에 제출했다. 세션 쿠키 대신 Bearer 토큰을 쓴 이유 — 확장 출처는 교차 사이트라 쿠키를 쓰려면 SameSite=None이 필요하고, 확장 하나 때문에 웹 전체의 CSRF 방어를 약화시킬 수는 없다.

그리고 비어 있는 운영 DB. 9/4에 운영 DB를 열었다. 회원·워치·알림 지표가 사실상 비어 있었고, 비로그인 사용량도 한 자릿수였다. 판정은 전환 실패가 아니라 유입 부재다 — 가설이 아직 시험대에 오른 적이 없다.

판단

두 번째 줄기는 정직함을 코드 밖으로 확장한 것이다. 보내지 않은 메일을 보냈다고 말하지 않기, coverage80 0.30을 가공 없이 공개하기, 약관이 코드와 다르게 말하던 세 곳(분당 한도·일일 한도·존재하지 않는 30초 제한)을 실제 값으로 정정하기. 화면과 문서도 파이프라인의 일부다 — 거기서 거짓말하면 데이터 정합성은 의미가 없다.

남은 항로

  • SES 프로덕션 액세스 — 재신청 문서까지 갖췄고 승인 대기. 풀리기 전까지 메일 유입은 계속 잠겨 있다
  • 확장 1.2.0 심사 결과 확인
  • 유입 실험 — "매일 알아서 도착합니다"라는 축에서는 무료 거대 서비스에 진다는 시장조사 결론에 맞춰 포지셔닝을 다시 세운다

이어지는 항로

LogbookAI 기반 뉴스 요약·감성분석 'AIB10'FleetAIB10 — AI 기반 뉴스 요약·감성분석 서비스Observatory크롬 웹 스토어 입항 — AIB10 확장 프로그램 v1.0.0 공개Observatory감성 모델 대수리 — 데이터 누수를 찾아서, 그리고 확장의 씨앗