Skip to content

v 3.5.0 - #423

Merged
GulSauce merged 6 commits into
mainfrom
develop
Jul 31, 2026
Merged

GulSauce merged 6 commits into
mainfrom
develop

Conversation

@GulSauce

Copy link
Copy Markdown
Member

No description provided.

GulSauce and others added 6 commits July 29, 2026 16:52
- RealBlankGrader: 정규화(NFKC+casefold+공백/문장부호 제거) + 빈칸별 인정집합 멤버십 채점 SSOT (FR-001·002·005·006)
- POST /grade 무상태 공개 채점 엔드포인트 — 결과·해설·기록 단일 판정 경로, 로그인 무관 (FR-006)
- Selection.acceptedAnswers JSON 임베드(마이그레이션 0건) + 저장·read carry, 구 문항 폴백(FR-009)
- POST /history score 서버 재계산(맞힌 문항 수), GET /history 텍스트 멤버십 판정 통일
- GET /problem-set REAL_BLANK 정답·인정집합 미노출(selections:[])
- 생성측 RealBlankQuizOrchestrator(ESSAY 미러링): 오답 미생성 + 빈칸별 acceptedAnswers 산출 (FR-008)
- 타 유형 GeminiQuestion 불변 → 채점·생성 완전 격리 (FR-007)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* ✨ feat: 생성 문제 본문 마크다운 서식 정합 보장 (005)

생성된 문제 본문의 마크다운(표·인용 등)이 원시 문법으로 노출되는 문제를
생성 단계에서 해결한다. 재현 버그의 직접 원인은 생성 소스의 개행 유실로
GFM 표가 한 줄에 글루되어 파싱되지 않는 것이었다(client 렌더 인프라는 정상).

- 프롬프트 서식 규약(MarkdownFormatGuideLine)을 전 퀴즈 타입 GuideLine 앞에
  SSOT로 prepend(QuizType.withLanguage 단일 주입점). 표 행별 개행·블록요소
  빈 줄·수식 $...$·리터럴 $·표셀 | 이스케이프·원시 HTML 금지 강제.
- 결정론적 정규화기(MarkdownNormalizer)를 저장 보편 초크포인트
  (AIProblemSetMapper.toQuizGeneratedFromAI)에 배치. 글루된 GFM 표의 개행을
  구분행 시그니처 앵커로 복원. 보수적·멱등·무손실 — 구분행 없으면 no-op
  (서식 없는 문제 회귀 0). 실제 생성·mock·fast-serve·전 타입 모두 통과.
- 서식 사유의 재생성·거부는 하지 않음(best-effort, 계약 열린질문 D).
- E2E 픽스처: mock 1번 문항을 표·인용·코드·수식 담은 마크다운으로 보강.
- 새 테스트 25개(정규화기 경계·매퍼 배선·프롬프트 규약 prepend).

계약: specs/005-fix-markdown-rendering/contract.md §7. 스키마 변경 없음.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* 🔥 refactor: 마크다운 서식 프롬프트 규약 제거 — 후처리기 단일 계층 (005)

사용자 확정 결정: 생성 프롬프트 예방 계층(MarkdownFormatGuideLine)을 두지 않고,
저장 시점 결정론적 정규화기만으로 범위를 좁힌다. 비결정적 LLM 준수에 의존하지 않고
재현 버그의 실제 실패 모드("글루된 표")만 결정론적으로 복구한다.

- MarkdownFormatGuideLine + QuizType prepend + QuizTypeFormatGuideLineTest 제거
- MarkdownNormalizer(저장 초크포인트) + 유닛테스트 13개는 유지
- 범위 밖(의도): 리터럴 $ 이스케이프·구분행 소실 표·블록수식 독립줄·머메이드

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
결과·해설 지점에서 방금과 같은 조건으로 새 세트를 이어 생성할 수 있도록,
세트가 자기 생성 조건을 온전히 소유하게 하고 그 조건을 되돌려주는 조회 통로를 낸다.

- ProblemSet에 page_numbers·language 저장(마이그레이션 V18, nullable). 기존
  quizType·totalQuizCount·customInstruction·fileUrl과 함께 세트가 생성 6종 조건을
  자기 소유하게 된다. legacy 세트는 두 값 null → 자동 US2 폴백 대상.
- GET /problem-set/{id}/regeneration-condition 신설(RegenerationConditionResponse).
  저장 조건을 그대로 반환하되 즉시생성/폴백 판정은 프론트가
  documentAvailable && pageNumbers && language로 수행(서버 집계 플래그 없음, 계약 얇게).
  조회는 공개(기존 getProblemSet과 동일 정책) — 게스트 이어풀기 지원.
  legacy 빈 pageNumbers는 응답에서 null로 정규화.
- documentAvailable은 자료 유효성 판정 자리로 두되 능동 만료검사는 이번 스코프
  미도입(후속) — 현 단계 항상 true. 자료 만료는 생성 시 SSE 에러로 표면화.
- 재현 트리거는 기존 POST /generation 재사용(전용 엔드포인트 없음) → 새 세트=독립 id.
- initProblemSet 시그니처를 pageNumbers·language까지 확장, 생성 흐름이 전달·저장.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: 빈칸 허용 정답 확충·노출 + 이어풀기 결함 수리 (007 백엔드)

US1 이어풀기 결함 수리:
- 같은 sessionId 재-POST를 멱등 no-op으로 흡수(sessionId unique 위반 시 예외 대신
  진행 중 생성 유지). SSE 재연결 시 클라이언트 onopen 재발사가 진행 중 생성을 죽이던
  근본 원인 차단. 재연결 replay(subscribe+Last-Event-ID)는 기존 자산 그대로.
- SSE heartbeat(keep-alive comment, 기본 15s) 추가 — 긴 TTFQ 무음 구간의 프록시 idle
  절단→재연결 트리거를 예방. EventSource가 comment 무시라 클라 무변.

US2 허용 정답 풍성화:
- RealBlankGuideLine §Step 2.6 + GeminiRealBlankQuestion.acceptedAnswers 서술 강화
  (통용 변형 적극 열거: 약어↔완전형 양방향·도메인 표준 이명·영↔한). FR-005 보수 가드 유지.
- acceptedAnswers 각 빈칸 index 0 = canonical 모범답 보장(프롬프트 규칙 +
  RealBlankGrader.grade 저장/노출 정규화). 모범답 누락 시 보강해 채점 멤버십도 보장.

US3 허용 정답 목록 노출:
- GradeResponse.GradeResult에 acceptedAnswers(string[][]) 추가. GradeService가
  인정집합(또는 legacy content 폴백)을 응답에 실음. 풀이화면 비노출 유지(FR-007).

E2E: MockAIServerAdapter가 REAL_BLANK 전략에서 REAL_BLANK 목업(정답 1선지 +
acceptedAnswers 2차원, 단일+다중 빈칸)을 산출 — 실 Gemini 없이 US2·US3·US1흐름 E2E.

재발방지 테스트: sessionId 재-POST 멱등 no-op / grade acceptedAnswers·index0·legacy
fallback / mockai REAL_BLANK 목업. 마이그레이션 없음.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: 요청 바디 Bean Validation 실패를 500이 아닌 400으로 응답 (007)

GlobalExceptionHandler에 MethodArgumentNotValidException(@Valid @RequestBody
검증 실패) 핸들러가 없어 catch-all(Exception→500)로 새어 "잘못된 요청"이 500으로
나가던 사전존재 갭을 교정한다(사용자 결정으로 007에 포함). 첫 필드 오류 메시지를
그대로 노출하고 없으면 INVALID_REQUEST 기본 메시지로 대체. 응답 바디는 기존
CustomErrorResponse 컨벤션 유지.

- ExceptionMessage.INVALID_REQUEST(400) 추가(폴백 메시지).
- GlobalExceptionHandlerTest: 빈 @notblank 바디 → 400 + 필드 메시지 회귀 테스트.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: 자기정리 write mock에 & !mockai 가드 — mockai 기능 E2E 이력 영속 (007)

MockQuizHistoryCommandService·MockQuizFolderCommandService 를
@Profile("mock") → @Profile("mock & !mockai") 로. 생성 mock과 동일 패턴이라
local,mock,mockai 기능 E2E에서 이 write mock들이 비활성돼 실제 서비스가 이력·폴더를
영속한다. 기존엔 이력 mock이 sentinel problem_set_id=0 + save→delete 라 mockai
기능 E2E에서 동시 이력 저장이 유니크 키 (user_id,0)에서 충돌해 POST /history 500이
났다(비차단이나 하네스 불일치). 부하 트레이스(mock, mockai 미사용)는 무영향.

- MockWriteServiceProfileTest: 두 mock의 & !mockai 가드 유지 회귀 잠금.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
같은 (user_id, problem_set_id)에 대해 결과 화면이 병렬로 열려 이력 저장이 동시에
일어나면, saveHistory의 비원자적 find-or-create가 둘 다 INSERT를 시도해 유니크 제약
uk_quiz_history_user_problem에 걸려 DataIntegrityViolationException → 500이 났다
(mock 없는 실 환경에서도 동시 저장 시 발생). initHistory와 동일한 try-save-catch-refind
패턴으로 충돌을 흡수해 먼저 만들어진 행을 재사용한다.

- saveHistory: find-or-create를 findOrCreateHistory 헬퍼로 추출(메서드-레벨
  @transactional 제거 — 각 리포지토리 호출이 독립 트랜잭션이라 INSERT 충돌이 흐름을
  오염시키지 않음). completeQuiz 변경은 명시적 save(merge)로 영속.
- 재발방지 테스트: 동시 INSERT 충돌 흡수(500 없이 기존 행 완료) + 기존 행 갱신 경로.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@GulSauce
GulSauce merged commit 924136f into main Jul 31, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant