Repository navigation
feat(reports): 관리자 회의록 재요약 엔드포인트 - #2
Conversation
- PipelineState.resummarizeDone/resummarizeFailed: done/failed→done|failed 재전이 - MeetingReport.replaceSummary/markResummaryFailed - ReportFinalizationService.resummarize: 저장 transcript+chat 재요약 - resolveAdminConfig + AdminGuard(Bearer) + POST /reports/:id/resummarize - e2e: 401/404/200 인증 케이스 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- POST /reports/:id/resummarize: 저장 transcript+chat 으로 재요약, summary 교체 - AdminGuard(Bearer + ADMIN_API_TOKEN)로 보호, 미설정 시 403 비활성 - done/failed 회의록 모두 대상(summaryStatus !== pending), pending 은 409 - 중간 pending 없이 done/failed 직접 전이 → 재요약 중 크래시에도 영구 pending 방지 - .env.template + deploy.yml 에 ADMIN_API_TOKEN 추가(env_file 로 컨테이너 전달) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces an administrator endpoint (POST /reports/:id/resummarize) to allow re-summarizing meeting reports, secured by a single admin API token and an AdminGuard using constant-time comparison. It updates the domain model (MeetingReport and PipelineState) to support direct transitions to done or failed states without an intermediate pending state, preventing permanent pending locks. The review feedback highlights a critical issue in the error handling of the resummarize service method: catching summarizer errors without rethrowing them leads to an incorrect 200 OK response on failure, and a failed re-summarization attempt on an already successful report would inappropriately downgrade its status to failed.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| } catch (err) { | ||
| const message = err instanceof Error ? err.message : String(err); | ||
| report.markResummaryFailed(message, this.deps.clock.now()); | ||
| await this.deps.repository.save(report); | ||
| } |
There was a problem hiding this comment.
1. 에러 전파 누락 및 HTTP 응답 일관성 문제\n현재 resummarize 메서드는 요약기(summarizer.summarize) 호출 중 에러가 발생하면 이를 catch하여 내부적으로 실패 상태만 기록하고 에러를 다시 던지지(throw) 않습니다. 이로 인해 관리자가 재요약 API(POST /reports/:id/resummarize)를 호출했을 때, 실제로는 요약에 실패했음에도 HTTP 200 OK 응답과 함께 실패 상태의 리포트 본문이 반환됩니다. 동기식 HTTP 요청-응답 사이클에서는 실패 시 적절한 5xx 에러(예: 500 Internal Server Error)를 반환하도록 에러를 상위 레이어로 전파하는 것이 올바릅니다.\n\n### 2. 기존 성공 데이터의 불필요한 상태 격하(Downgrade) 위험\n이미 성공적으로 요약이 완료된(done) 회의록을 대상으로 재요약을 시도하다가 일시적인 LLM 장애 등으로 실패할 경우, 기존의 성공적인 요약본이 보존되더라도 상태가 failed로 변경되어 사용자 화면에서 "요약 실패"로 표시되는 심각한 UX 퇴행이 발생할 수 있습니다. 기존 상태가 done이었던 경우에는 상태를 failed로 격하하지 않고 기존의 성공 상태를 유지하도록 에러만 던지고 DB 저장을 건너뛰는 것이 안전합니다.\n\n참고: 이 변경을 적용하면 관련 유닛 테스트(재요약 Summarizer 가 throw 하면...)도 예외 발생을 기대하도록 수정되어야 합니다.
} catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n // 기존 상태가 이미 'done'인 경우, 일시적 요약 실패로 인해 기존 성공 상태를 'failed'로 격하하지 않도록 보호합니다.\n if (report.pipeline.summaryStatus === 'failed') {\n report.markResummaryFailed(message, this.deps.clock.now());\n await this.deps.repository.save(report);\n }\n throw err;\n }리뷰 반영: - 동기 HTTP 요청이므로 summarizer 실패를 삼키지 않고 전파(상위에서 5xx). - 실패 시 상태 변경/저장을 하지 않아 done 회의록이 failed 로 격하되는 UX 퇴행 방지. done→done, failed→failed 그대로 보존. - 더 이상 쓰이지 않는 실패 전이(PipelineState.resummarizeFailed / MeetingReport.markResummaryFailed) 및 관련 spec 제거. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces an administrative endpoint (POST /reports/:id/resummarize) to allow re-summarizing meeting reports using saved transcripts and chat logs, secured by an AdminGuard using a single ADMIN_API_TOKEN. The feedback focuses on two main areas: first, ensuring that resummarization is only permitted when the STT stage has successfully completed (sttStatus === 'done') to prevent empty inputs to the LLM; second, addressing a potential timing attack vulnerability in safeTokenEqual by hashing both tokens before comparison to avoid leaking token length information.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
리뷰 반영: - 재요약 조건에 sttStatus === 'done' 추가. STT 실패/미완료(transcript 없음) 회의록은 빈 입력 LLM 호출/비정상 전이를 막기 위해 409 로 거부. - ReportNotResummarizableError 를 reason 기반으로 일반화(STT 미완료 / 요약 진행 중). - safeTokenEqual: 두 토큰을 SHA-256(고정 32B)으로 해시 후 timingSafeEqual 비교. 길이 차 조기 반환 제거로 토큰 길이 타이밍 누출 차단. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
개요
회의록 요약을 다시 생성하는 관리자 전용 엔드포인트
POST /reports/:id/resummarize를 추가합니다. 요약이 실패한 회의록 재시도 + 이미 성공한 회의록을 새 프롬프트로 재요약(A/B) 하는 용도입니다.인증
Authorization: Bearer <ADMIN_API_TOKEN>헤더를AdminGuard가 검증.ADMIN_API_TOKEN미설정 시 엔드포인트 자동 비활성(403) → 무방비 노출 방지. 토큰 불일치는 401.동작
summaryStatus !== 'pending'(done + failed 모두). 진행 중(pending)이면 409.SummarizerPort로 재요약 → 기존 summary 교체.ReportDetailResponse.테스트
TDD red(066f644) → green(b4f9590). 백엔드 652개 unit + reports e2e green, lint/build OK.
운영 활성화
ADMIN_API_TOKEN등록 (미설정이면 운영에서 403 으로 안전).apps/backend/.env에ADMIN_API_TOKEN설정