문제 추가 [추천] 기능 — 내부 풀 P2 하이브리드 추천 + 새로고침 rotation
요약문제 추가 모달 검색 스텝에 [추천] 문제 제안 기능 추가. 콜드스타트(실사용자 적음) 대응으로 P2 하이브리드 채택 — 내부 문제 풀 3-tier 매칭(난이도+태그 → 난이도만 → 시드 폴백)에 서버 묶음 prefetch(8) + 클라이언트 [새로고침] rotation. 기본 1개 노출. PR #479 `4b60583`(squash), 23파일 +1729/−16. Critic 3라운드 findings(P2·P2·P3) 전부 수정, 최종 라운드 ACP turn-reap로 판정 유실 → 로컬 판정 채택(findings 전부 닫힘). 병렬 Oracle 세션이 전 파이프라인을 선행 완료해 Oracle은 재구축 없이 독립 검증→Critic→머지로 전환.
날짜: 2026-07-21
목표
문제 추가 모달(검색 → 확인 2스텝)의 검색 스텝에 추천 문제 제안을 추가한다. 사용자가 스터디에 맞는 문제를 빠르게 찾도록 난이도·태그 기반 후보를 제안한다.
배경 / 제약: AlgoSu 실사용자 수가 많지 않아 내부 문제 풀이 얇다(콜드스타트). 외부 플랫폼(프로그래머스/BOJ) 공식 추천 API도 부재. → 빈 목록/빈약한 추천이 UX 리스크.
결정 사항
D1. 추천 소스 = 내부 문제 풀 (P2), 외부 API 미의존
이미 등록된 다른 스터디/주차 문제 중 이 스터디에 없고 난이도·태그가 맞는 것을 제안. Problem 엔티티의 difficulty·level·tags·category로 추천 기준 데이터 확보. 외부 크롤링/solved.ac 우회 없이 안정적.
D2. 콜드스타트 폴백 = 3-tier 단계적 축소
recommendForStudy(services/problem/src/problem/problem.service.ts):
- 1순위 — 난이도대 + 태그 매칭 내부 문제
- 부족 시 — 태그 조건 풀고 난이도대만 매칭
- 그래도 부족 시 — 큐레이션 시드 목록(
recommendation-seeds.ts, 난이도별 대표 문제, 정적 상수) 폴백
시드 폴백으로 외부 API 의존 0 + 사용자 적어도 "빈 화면" 없음. 풀이 커지면 자연히 1·2순위 비중 상승 → 지금 만들어도 안 버려지는 구조.
D3. 노출 1개 + [새로고침] rotation, 하이브리드 prefetch
- 기본 추천 1개만 노출(콜드스타트에 가장 강함 — 후보 1개만 있으면 됨).
- **[새로고침]**으로 다음 후보 rotation. 하이브리드: 모달 열 때 서버서 후보 묶음(8) 1회 조회(
use-problem-recommendation.ts) → 새로고침은 클라이언트 rotation(즉시·조회 0), 묶음 소진 시 다음 묶음 재조회. 순수 매번조회(지연)·순수 1회묶음(소진 후 반복)의 단점 제거.
D4. 보안 — cross-study 후보 스코핑
내부 후보 쿼리(findRecommendationCandidates): select 화이트리스트로 description 미포함(문제 본문 누출 방지, Sprint 252 정책 정합), studyId: Not(excludeStudyId)로 자기 스터디 제외, In([]) 전체스캔 방어. exclude(이미 본) 목록은 @ArrayMaxSize(100)(RecommendQueryDto) 상한.
완료 항목
b5b83e2계열 아님 — PR #4794b60583(squash), 23파일 +1729/−16- BE(
services/problem): recommend 엔드포인트(GET /recommendations,RecommendQueryDto) +recommendForStudy3-tier +recommendation-seeds.ts+ dual-write 갱신 - FE:
use-problem-recommendation.ts(prefetch-8 rotation + shownUrls 캡핑),SearchStep.tsx추천 섹션,ConfirmStep.tsxplatform 정합,problem-search.utils.ts, i18n ko/en
- BE(
검증(Oracle 직접 재검 — 자기보고 불신): BE tsc(feature 클린)·ESLint 0·jest 19 suites/245 pass(problem.service.ts 99.05%/97.97%, seeds 100%, 컨트롤러 100%). FE tsc 0·lint Errors 0·jest 6 suites/95 pass.
Critic(Codex gpt-5.5, base 9c5d188): 3라운드 findings 전부 수정 — 재위임은 규칙대로 Curator.
- R1 [P2]
SearchStep.tsx— 추천 선택 시 소스 플랫폼이 현재 탭 기준 → 추천 데이터 기준으로 수정(d2345c6) - R2 [P2]
use-problem-recommendation.ts— 새로고침 반복 시shownUrls무한 증가 → 백엔드 상한(100) 이하 캡핑(e25ef1b) - R3 [P3]
ConfirmStep.tsx— confirm 표시가 탭 기준(추천 platform 미반영) →effectivePlatform실제 platform 기준 렌더(ecf19b1) - R4 최종 — ACP turn-reap로 판정 미출력 → 로컬 판정 채택(앞선 findings 전부 닫힘, diff 동일)
인시던트
- 병렬 Oracle 세션 선행 완료: Oracle이 위임 컨텍스트를 잡는 사이 병렬 세션이 전 파이프라인(커밋 2개→push→PR #479)을 이미 완료. 확정 스펙(P2 3-tier·seed·prefetch 8·rotation)과 정확히 일치 → Oracle은 블라인드 재구축 없이 독립 검증→Critic→머지로 전환.
- Critic ACP turn-reap 반복:
run_in_background·nohup완전분리 모두 세션 경계에서 SIGKILL → 판정 성공률 불안정(4라운드 중 3회 판정 출력, 1회 유실). watchdog "비정상 종료 감지" 알림을 완료 알림과 구분 필요. - Trivy Scan — problem FAILURE:
brace-expansion·js-yamlHIGH DoS 전이 의존성 CVE — 이번 기능 무관(diff 미포함), main에도 존재하는 드리프트. 비필수 게이트라 머지 가능, 별도 PR로 범프 예정.