Files
reloop-v2/.qa/QA-2.md
reloop 40232af429 qa: breezing audit — 6 tasks, 10 critical + 9 major findings
- QA-1 Backend API contract: APPROVE (minor type contract 2)
- QA-2 Persona forget algorithm: APPROVE (math verified)
- QA-3 Auth & security: REQUEST_CHANGES
  - email normalize missing / JWT revalidate / secret fallback / whitelist
- QA-4 Frontend runtime: REQUEST_CHANGES
  - review double-submit race / subjects/[id] NaN / history error states / native alerts
- QA-5 Prisma & transactions: REQUEST_CHANGES
  - reviews.submit snapshot outside tx / tag cascade data loss / iteration race
- QA-6 Build & deploy: REQUEST_CHANGES
  - missing migrations, postinstall, lockfile, PM2 ecosystem

Follow-up fix proposals saved to .claude/state/pending-fix-proposals.jsonl (18 tickets)
2026-04-11 23:58:37 +09:00

3.1 KiB
Raw Blame History

QA-2 Report — Persona Forget Algorithm Verification

Worker: Explore agent (ultrathink) · Lead review: 승인

Summary

  • verdict: APPROVE
  • critical: 0, major: 0, minor: 2

상수 검증

Constant Expected Actual OK?
PERSONA_LAMBDA.senior 0.1 0.1
PERSONA_LAMBDA.mid 0.2 0.2
PERSONA_LAMBDA.junior 0.4 0.4
PERSONA_LAMBDA.crammer 0.6 0.6
INTENSITY_THRESHOLD.strict 0.7 0.7
INTENSITY_THRESHOLD.moderate 0.5 0.5
INTENSITY_THRESHOLD.relaxed 0.35 0.35
DEFAULT_K 4.0 4.0
DEFAULT_INITIAL_S0 0.3 0.3
MAX_INTERVAL_DAYS 60 60

수식 유도 검증

목표: σ(k·(S₀·e^(-λt) - D)) = P_threshold 를 t에 대해 풀기.

  1. σ⁻¹ 적용: k·(S₀·e^(-λt) - D) = logit(P_threshold)
  2. S₀·e^(-λt) = D + logit(P_threshold)/k (== target)
  3. e^(-λt) = target / S₀
  4. t = -(1/λ)·ln(target / S₀)

코드(persona-forget.service.ts:131):

const tDays = -(1 / lambda) * Math.log(target / s0);

✓ 수식과 코드가 정확히 일치.

경계 조건 검증

Case 예상 동작 코드 경로 OK?
s0 ≤ 0 즉시 (1h) 스케줄 103-109
target ≤ 0 60일 cap 114-120
target ≥ s0 ln ≤ 0 → t ≤ 0 → 즉시 122-128
0 < target < s0 정상 공식 131
t > 60 Math.min(tDays, MAX_INTERVAL_DAYS) 132
logit(0) / logit(1) ε clamp 192
predictP at t=0 σ(k(s0 - D)) 150-153

모든 경계 조건에서 수학적 domain error 없음.

updateS0 검증

prev result formula expected OK?
0.2 correct 0.2·0.5+0.6 0.70
1.0 correct clamp(1.1) 1.0
0.8 incorrect 0.8·0.5 0.40
0.4 partial 0.4·0.7+0.3 0.58
null correct 0.3·0.5+0.6 0.75 ✓ (기본값 0.3에서 시작)

Findings

[minor] 스펙 커버리지 — persona × intensity matrix 불완전

  • Location: backend/src/forget/persona-forget.service.spec.ts:40-130
  • Evidence: senior vs crammer 비교, 3개 intensity 개별 테스트는 있지만 4 persona × 3 intensity = 12 조합을 grid로 돌리는 테스트가 없음.
  • Impact: 공식상 persona/intensity는 단순 lookup이라 회귀 가능성 낮음. 다만 CI 신뢰도 측면에서 보완 권장.
  • Suggested fix: describe.each([...]) 로 matrix 테스트 추가.

[minor] study-logs / reviews 에서 lastUpdatedAt: new Date() 중복 생성

  • Location: backend/src/study-logs/study-logs.service.ts:96, 115, backend/src/reviews/reviews.service.ts:87, 106
  • Evidence: snapshot upsert 와 schedule() 호출이 각각 new Date()를 독립 생성. 수 ms 차이 발생 가능.
  • Impact: 스케줄 시각이 수 ms 어긋남. 무시 가능 수준이지만 의미가 모호해짐.
  • Suggested fix: const now = new Date() 를 함수 진입부에서 한 번만 생성.

Verdict

APPROVE. 알고리즘의 수식·경계·consumer 호출 모두 정확. minor 2건만 follow-up 개선 권장.