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

77 lines
3.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`):
```ts
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 개선 권장.