일기 slecs

P0 두 개를 발견한 날, 나머지는 그냥 쏟아졌다

목차

오늘 제일 먼저 건드린 건 kp-pay였다. 아침부터 자금흐름 전수감사를 돌린다는 게 이미 예사로운 하루가 아니라는 뜻이었다.

감사가 끝나기 전엔 아무 것도 모른다

kp-pay v2.0 대응 작업을 하다 보니 기존 코드가 얼마나 검증됐는지 확신이 없어졌다. 페이 관련 코드는 "동작한다"와 "안전하다"가 전혀 다른 말인데, v2.0으로 API 스펙이 바뀌면서 예전에 짠 가정들이 조용히 틀어졌을 가능성이 있었다. 그래서 손을 댄 게 자금흐름 전수감사 - ap/pay/web, batch/withdrawal, co/member/web, co/payment/web, co/wallet/web, utl 전체를 훑었다.

결과가 나왔을 때 P0 2개를 보고 잠깐 멈췄다.

P0이 하나도 아니고 둘. 보안·정합 양쪽에서 나온 것들이었다. P1이 7개, P2가 9개. 총 18개 이슈를 한 커밋으로 때려 넣었다는 게 커밋 메시지에 그대로 박혀 있다. 하나씩 뭐가 문제였는지는 커밋에 다 담지 않았지만, 감사를 돌리기 전엔 그 숫자들이 존재하는지조차 몰랐다는 게 사실이다. 모르는 채로 v2.0 런칭을 밀어붙였으면 어떻게 됐을지 생각하면 오늘 감사를 먼저 한 게 맞았다.

배치 출금 쪽(batch/withdrawal)에서 나온 이슈가 특히 불편했다. 배치는 사람 없이 돌아가는 코드라 버그가 생겨도 발견이 늦다. 정합 문제가 거기서 터지면 출금 이중처리나 미처리가 조용히 쌓인다. utl 유틸리티 레이어도 결제 공통 로직을 들고 있어서 거기서 나온 이슈는 파급 범위가 넓었다. 전부 고쳤다. 그리고 나서야 v2.0 기능 작업을 이어갔다.

A팝업, B서버 - 발급방식 두 갈래를 하나로

가상계좌 발급 방식이 v2.0에서 두 가지로 갈렸다. A방식은 팝업에서 발급하는 흐름이고, B방식은 서버에서 직접 발급한다. 이전엔 하나였는데 이걸 분기로 처리하면서 charge.jspreauth.jsp가 같이 손봐야 했고, 쿼리 매퍼(sqlmap slecs/ap/)도 바뀌었다.

A팝업은 기존 흐름에 가깝다. 사용자가 팝업을 통해 계좌정보를 확인하고 진행하는 방식. B서버는 팝업 없이 서버가 바로 발급을 처리하는 방식인데, 이 경우 프론트 상태와 서버 상태를 맞추는 게 조금 더 까다롭다. 응답 시점에 UI가 뭘 보여줘야 하는지 타이밍 문제가 생기기 때문이다. wallet/webmember/web 양쪽 컨트롤러를 건드린 건 이 두 발급방식이 각각 다른 진입 경로를 타기 때문이었다.

그리고 충전 KYC 게이트 버그가 이 과정에서 발견됐다. 가상계좌 발급 전에 KYC 검증이 제대로 안 걸리는 케이스가 있었다. 어떤 경로로 들어오면 게이트를 우회할 수 있었던 것이다. 이걸 잡는 김에 KpPayUtilMockTest.java도 새로 썼다. 유틸리티 레이어가 v2.0 API 변경사항을 제대로 처리하는지 목 테스트로 검증하는 파일이다. 보안 감사 + 기능 완성 + 테스트 신규 작성이 한 블록으로 묶인 셈이었다.

payCharge.jsp - 배선이 안 됐던 것

이커머스 PG 플랫폼 쪽 payCharge.jsp에서 kp-pay로 가는 흐름이 끊겨 있었다. KPPayForm 흐름이 연결이 안 된 상태였다는 게 fix 커밋에 드러난다. 이건 v2.0 대응 과정에서 이커머스 PG 플랫폼 레이어를 건드리다가 배선이 빠진 건데, 직접 흐름을 따라가 보지 않으면 잡기 어려운 종류다. 특히 PG 플랫폼 레이어는 공통 컴포넌트를 많이 끌어다 쓰기 때문에, 변경 후 배선을 다시 확인하지 않으면 조용히 죽어있는 상태로 남는다.

여기까지 다 정리하고 나서 오늘 kp-pay 관련 현황을 문서로 정리했다. jeju-v2.0-e2e-status-20260708.md - A/B 발급방식 현황, 남은 실키검증 항목들을 명세로 남겨뒀다. 이런 문서가 없으면 내일 다시 작업할 때 "어디까지 됐더라"를 코드에서 역추적해야 한다. 귀찮더라도 오늘 상태에서 적어두는 게 맞다.

recipe가 오늘 갑자기 폭발했다

오전에 kp-pay를 붙들고 있다가 오후로 넘어오면서 admin recipe쪽을 건드렸는데, 여기서 커밋이 우르르 나왔다.

처음엔 참고사이트 분석 기능이었다. 레시피 작성할 때 참고할 사이트 URL을 입력하면 재료를 자동으로 추천해주는 흐름. analyze API 라우트를 새로 만들고 RecipeBuilder.tsx에 연결했다. 여기까지는 깔끔한 단일 기능이었는데, 분석 결과가 너무 단순한 것 같아서 LLM 심층분석을 추가했다. 같은 라우트에 LLM 레이어를 얹었다. 이제 URL 하나 넣으면 재료 추천까지 자동으로 나온다.

그 다음이 디자인 토큰이었다. 색상 5개 + 버튼 radius + 폰트 설정을 토큰으로 관리하면서 실시간으로 WCAG 대비비를 배지로 보여주는 기능. 대비비를 직접 계산해서 AA/AAA 달성 여부를 즉시 표시한다. 이게 생각보다 유용했다. 디자이너 없이 색을 고를 때 "이거 접근성 기준 통과하나?"를 그냥 눈으로 보면 되니까.

거기서 멈추면 될 것 같았는데 팔레트 프리셋이 또 생겼다. 8종. RGB를 직접 안 골라도 되게 하려고 만든 프리셋인데, 쓰다 보니 프리셋만으로는 조합이 부족한 느낌이 들었다. 그래서 coolors 식 팔레트 자동생성 기능을 추가했다. 🎲 버튼으로 랜덤 팔레트를 생성하고, 마음에 드는 액센트 색은 잠금을 걸 수 있다. 다크/라이트 모드 변환도 넣었다. 이건 처음부터 설계하고 만든 게 아니라, 쓰다 보니 "이게 있으면 좋겠다"가 쌓여서 커밋이 하나씩 늘어난 케이스다.

버그도 두 개 나왔다. 타입 칩을 끄면 그 타입에 속하는 재료가 해제돼야 하는데, 여러 타입에 걸친 재료는 겹치는 타입이 아직 켜져 있으면 유지되어야 한다. 이 로직이 단순 토글로 구현돼 있어서 틀렸다. 수정했다. 카테고리 체크박스 쪽도 3-state로 고쳤다. 카테고리를 끄면 하위 재료가 전부 해제되는 게 맞는데, 기존엔 카테고리만 꺼지고 하위 재료 상태는 남아있었다. 이런 버그는 써보지 않으면 안 보인다.

recipe 관련 커밋만 7개. 오늘 recipe에 꽤 많은 시간을 쏟았다.

잡일들이 생각보다 많았다

kpopdex AdSense에서 저품질 판정이 들어왔다. 멤버 페이지 중에 bio가 300자 미만인 것들이 얇은 콘텐츠로 분류된 것이다. 대응이 간단했다. MemberPage.astro에서 bio 기준 미달 페이지는 noindex 처리하고, sitemap-content.xml.ts에서 사이트맵 제외. CLAUDE.md에도 기록해뒀다. 이런 식의 AdSense 대응은 판단이 어렵지 않고 구현도 단순한데, 안 하면 계속 수익에 영향을 준다.

social 봇 쪽은 소재고갈이 문제였다. 뉴스 소스를 추가하고 컴백 윈도우를 7일에서 30일로 늘렸다. 그리고 최신순+7일 창으로 뉴스를 가져오게 바꿨다. 커서 방식을 제거한 것도 스테일 백로그가 쌓이는 원인이었기 때문이다. 커서가 있으면 오래된 것들도 계속 큐에 들어오는데, 7일 창으로 잘라버리면 그냥 최신 것만 본다. 단순하지만 효과적인 변경이다. content.pypost_daily.py 두 파일이 같이 수정됐다. 두 커밋에 걸쳐서 들어간 건 첫 커밋 이후 뉴스소스 추가를 한 번 더 했기 때문이다.

appstore-sales 대시보드는 구글 플레이 설치수를 추가했다. 무료 설치 포함. dashboard-stats.ts에서 데이터를 끌어와서 페이지에 표시하고, 상단 KPI를 앱스토어 다운로드와 구글 플레이 설치가 대칭으로 보이게 재배치했다. 이건 데이터가 있으니까 보여주는 것이지 별 결정이 필요한 작업은 아니었다. 그런데 KPI 레이아웃을 대칭으로 맞추니까 확실히 보기 좋아졌다.

그 데이터를 만들기 위한 수집기는 별도로 작업했다. playstore-installs-check.py + cron. 구글 플레이 설치통계를 무료 포함으로 주기적으로 수집해서 cms_playstore_installs에 넣는 흐름이다.

psy 쪽 심리테스트 자동생성은 매일 돌아가는 배치 결과다. 오늘은 경계스타일, 영혼 고대사원, 도시전설 주인공 테마로 3개가 생성됐다. 커버 이미지도 같이 생성됐다. 이쪽은 이미 자동화가 돼 있어서 내가 직접 한 건 없다.

외주 레포를 처음 열었다

wishket + kmong 입찰 운영을 위한 레포를 오늘 처음 초기화했다. kmong 쪽 gig 페이지(gig_anything_dev.html)와 섬네일 파일들이 초기 커밋에 들어갔다. 이건 새로운 영역의 시작점이다. 외주 입찰을 체계적으로 관리하겠다는 결정.

바로 wishket 입찰 지원서도 작성했다. FORMFACTORY 서면설계 접수 플랫폼 백엔드 구축 건. bid_formfactory_backend.html에 지원서를 담고 포트폴리오 링크도 연결했다. PORTFOLIO.md도 같이 갱신했다.

그리고 데모를 두 개 만들었다. 하나는 세미나·가족행사 랜딩 + Meta 픽셀/전환API 연동 데모, 하나는 FORMFACTORY 백엔드 작동 데모. 두 데모 모두 index.html 포트폴리오 메인에 카드로 등록됐다. 세미나 랜딩 데모는 한 번 수정이 더 있었는데, 제목에 em-dash가 들어간 걸 하이픈으로 바꿨다. 사소한 것 같지만 AI 티 제거 규칙에 걸리는 항목이라 그냥 뒀다가 나중에 혼나는 것보다 바로 고쳤다.

포트폴리오 카드 공통으로 desc가 길면 4줄 클램프 + 더보기/접기 토글도 달았다. 이게 없으면 설명이 긴 데모 카드가 레이아웃을 망가뜨린다.

하루가 이렇게 길었다

되짚어보면 오늘 커밋이 30개에 가깝다. kp-pay 전수감사가 제일 무거웠고, recipe가 예상보다 길어졌고, 외주 레포 개설이 새로운 일의 시작이었고, 나머지는 유지보수와 자동화였다.

P0 두 개를 발견한 게 오늘의 핵심이라고 생각한다. 발견하고 고쳤으니 다행이지만, 그게 있었다는 사실 자체가 자금흐름 코드를 더 자주, 더 꼼꼼히 감사해야 한다는 신호다. 전수감사가 일회성이면 의미가 없다. 다음번 v2.0 피처 추가할 때도 같은 수준으로 돌려야 한다.

recipe는 오늘 같은 방식으로 기능이 늘어날 때가 좋기도 하고 경계하게 되기도 한다. 쓰다 보면 "이것도 있으면 좋겠다"가 자꾸 나와서 설계 없이 커밋이 붙는다. WCAG 배지나 coolors 식 자동생성은 진짜 유용하지만, 어느 순간 RecipeBuilder가 너무 커지면 손대기 어려워진다. 오늘은 그 경계까지 도달하진 않았지만 다음번엔 조금 정리가 필요할 수 있다.

외주 레포를 처음 열었다는 건 운영 방식이 하나 추가됐다는 뜻이다. 입찰 지원서를 파일로 관리하고 데모와 연결하는 흐름을 만든 것 자체가 오늘 새로 만든 인프라다. 이게 앞으로 얼마나 쓸모 있어질지는 두고봐야 알겠지만.

댓글 0

첫 댓글 달아줘.