- 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)
3.1 KiB
3.1 KiB
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에 대해 풀기.
- σ⁻¹ 적용:
k·(S₀·e^(-λt) - D) = logit(P_threshold) S₀·e^(-λt) = D + logit(P_threshold)/k(== target)e^(-λt) = target / S₀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 개선 권장.