Skip to content

feat: 중단된 면접 이어하기 (B-5) - #180

Merged
i3months merged 4 commits into
devfrom
feature/resume-interrupted-session
Aug 19, 2026
Merged

feat: 중단된 면접 이어하기 (B-5)#180
i3months merged 4 commits into
devfrom
feature/resume-interrupted-session

Conversation

@i3months

Copy link
Copy Markdown
Member

변경 사항

감사 백로그 B-5. 마지막 남은 항목이다. PATCH /api/sessions/{id}/resume 로 INTERRUPTED 세션을 다시 진행 가능한 상태로 되돌린다.

완료·취소 세션은 이어할 수 없다(422). 완료는 피드백이 이미 나갔고, 취소는 시작한 적이 없다 — 둘 다 기존 /retry 로 새 세션을 만드는 게 맞다.

핵심은 상태 전이가 아니라 끊긴 턴 복구다

INTERRUPTED → IN_PROGRESS 만 열면 되는 줄 알았는데 아니었다. 중단은 보통 턴 한가운데에서 일어나고(탭 종료·네트워크 끊김·수동 중단), 그동안 도착한 콜백은 isTerminal() 가드가 전부 드롭했다. 상태만 되돌리면 사용자는 답할 질문이 없거나 "(생성 중)" 에 멈춘 화면을 본다.

SessionResumeService.recoverTurn 이 마지막 메시지로 분기한다:

마지막 메시지 조치 안 하면
정상 질문 없음 — (그 질문에 답하면 된다)
"(생성 중)" placeholder failFollowup + 다음 일반질문 화면이 생성 중에 영구 고착
자기소개 답변 + 풀 0건 SelfIntroAnsweredEvent 재발행 POOL_EXHAUSTED 로 세션이 그대로 끝남
그 외 답변 다음 일반질문 답할 질문이 없어 아무것도 못 함

자기소개 분기가 특히 중요하다. 자기소개만 답하고 끊긴 경우 질문 풀 생성 요청이 유실됐을 수 있는데, 이때 advanceToNextGeneral 로 넘기면 풀이 비어 있어 세션이 즉시 종료된다.

시간 한도 — resumed_at (V27)

max_duration_minutesstarted_at 기준으로 재고 있어서, 한참 뒤에 재개하면 스위퍼가 즉시 다시 중단시킨다. 어제 중단한 면접을 오늘 이어가면 재개하자마자 끝난다.

resumed_at 을 추가하고 스위퍼가 durationAnchor()(= resumedAt ?? startedAt) 기준으로 재도록 했다. 이어하기마다 그 자리(sitting)의 시간이 새로 시작되고, started_at 은 '처음 시작한 시각'으로 보존된다(히스토리 표시용).

트레이드오프: 이어하기를 반복하면 총 시간이 늘어난다. 대안은 누적 활동시간 추적(컬럼 추가 + 모든 종료 경로에서 갱신)인데, 연습 도구에서 총 시간을 엄격히 제한할 이유가 약해서 단순한 쪽을 골랐다. 필요하면 나중에 바꿀 수 있다.

전이

resumeIfInterrupted 조건부 UPDATE 로 전이를 차지하고 ended_at 을 지운다. 다른 전이(startIfReady/cancelIfReady/finishIfInProgress)와 같은 패턴이라 중복 요청(더블클릭·재전송) 중 하나만 1을 받는다.

프론트

  • 종료 패널: 중단 세션에 '이어서 진행하기' 를 primary 로. 하던 대화가 그대로 남아 있으니 새로 만드는 것보다 이어가는 게 먼저다. '같은 설정으로 다시' 는 secondary 로 내렸다
  • 히스토리 카드: 중단 세션 CTA 를 '기록 보기 →' 에서 '이어하기 →' 로
  • 서버가 끊긴 턴까지 복구하므로(다음 질문 발행 등) 세션과 메시지를 모두 무효화해야 화면이 살아난다

테스트

  • SessionResumeServiceTest 신설 (8) — 완료 세션 거부, 타인 세션 거부, 전이 경합, 복구 분기 5종(정상 질문 무개입 / placeholder 실패 처리 / 답변 후 다음 질문 / 자기소개+풀 없음 → 재요청 / 자기소개+풀 있음 → 다음 질문)
  • SessionEndedPanel.test.tsx +2 — 중단 세션의 이어하기 호출, 완료 세션엔 미노출
  • SessionCard.test.tsx — CTA 문구 갱신

backend test 통과(ArchUnit 포함) · frontend vitest 113/113 · eslint 0 · tsc · build.

영향 범위

  • DB 마이그레이션: 있음 (V27 — resumed_at 컬럼 추가만. 기존 행은 NULL = 기존과 동일 동작)
  • API contract 변경: 추가만 (PATCH /api/sessions/{id}/resume)
  • 동작 변경: 스위퍼의 시간 한도 기준이 started_atCOALESCE(resumed_at, started_at). 재개한 적 없는 세션은 그대로다
  • 환경변수: 없음

리뷰어 체크포인트

  • 이어하기 반복 시 총 시간 무제한 — 위에 적은 트레이드오프. 제한이 필요하면 누적 시간 추적으로 바꿔야 한다
  • 자기소개 답변 후 풀 재요청은 풀이 0건일 때만 한다. 풀이 있는데 다음 질문만 못 받은 경우와 구분된다
  • INTERRUPTEDisTerminal() 에 남아 있다 — 재개하면 IN_PROGRESS 라 가드를 정상 통과하므로 그대로 뒀다. 중단 상태에서 늦게 오는 콜백은 여전히 드롭되는 게 맞다

`PATCH /api/sessions/{id}/resume` — INTERRUPTED 세션을 다시 진행 가능한 상태로 되돌린다.
완료·취소 세션은 이어할 수 없다(422). 새로 하려면 기존 `/retry` 를 쓴다.

## 핵심은 상태 전이가 아니라 끊긴 턴 복구다

중단은 보통 **턴 한가운데**에서 일어난다(탭 종료·네트워크 끊김·수동 중단). 그동안 도착한
콜백은 terminal 가드가 전부 드롭했으므로, 상태만 IN_PROGRESS 로 되돌리면 사용자는
답할 질문이 없거나 "(생성 중)" 에 멈춘 화면을 본다.

`SessionResumeService.recoverTurn` 이 마지막 메시지로 분기한다:

| 마지막 메시지 | 조치 |
|---|---|
| 정상 질문 | 없음 — 그 질문에 답하면 된다 |
| "(생성 중)" placeholder | `failFollowup` + 다음 일반질문 (그 꼬리질문은 영영 안 온다) |
| 자기소개 답변 + 풀 0건 | `SelfIntroAnsweredEvent` 재발행 (풀 생성 요청이 유실된 것) |
| 그 외 답변 | 다음 일반질문 |

자기소개 분기가 중요하다. 풀이 없는데 `advanceToNextGeneral` 로 넘기면 POOL_EXHAUSTED 로
세션이 그대로 끝나버린다.

## 시간 한도

`max_duration_minutes` 를 `started_at` 기준으로 재고 있어서, 한참 뒤에 재개하면 스위퍼가
즉시 다시 중단시킨다. V27 로 `resumed_at` 을 추가하고 스위퍼가
`durationAnchor()`(= resumedAt ?? startedAt) 기준으로 재도록 했다 — 이어하기마다 그 자리의
시간이 새로 시작된다. `started_at` 은 '처음 시작한 시각'으로 보존된다(히스토리 표시).

이어하기를 반복하면 총 시간이 늘어난다. 연습 도구라 제한하지 않았다.

## 전이

`resumeIfInterrupted` 조건부 UPDATE 로 전이를 차지하고 `ended_at` 을 지운다. 다른 전이
(start/cancel/finish)와 같은 패턴이라 중복 요청 중 하나만 1을 받는다.

## 테스트

`SessionResumeServiceTest` 신설 (8) — 완료 세션 거부, 타인 세션 거부, 전이 경합,
그리고 복구 분기 5종(정상 질문 무개입, placeholder 실패 처리, 답변 후 다음 질문,
자기소개+풀 없음 → 재요청, 자기소개+풀 있음 → 다음 질문).
- 종료 패널: 중단 세션에 '이어서 진행하기' 를 primary 로. 하던 대화가 그대로 남아 있으니
  새로 만드는 것보다 이어가는 게 먼저다. '같은 설정으로 다시' 는 secondary 로 내렸다
- 히스토리 카드: 중단 세션 CTA 를 '기록 보기 →' 에서 '이어하기 →' 로
- `useResumeSession` — 서버가 끊긴 턴까지 복구하므로(다음 질문 발행 등) 세션과 메시지를
  모두 무효화해야 화면이 살아난다

테스트: `SessionEndedPanel.test.tsx` +2(중단 세션의 이어하기 호출, 완료 세션엔 미노출),
`SessionCard.test.tsx` CTA 문구 갱신. vitest 113/113 · eslint 0 · tsc · build 통과.
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