음성 재생과 운영 배포 정리

This commit is contained in:
Yun Chan 2026-06-28 12:18:20 +09:00
parent 8ed185ce6c
commit ac7db95542
1020 changed files with 46863 additions and 2175 deletions

View file

@ -268,14 +268,16 @@ OpenAI STT/TTS 어댑터(순수 변환 + voice preset 매핑). 상담 로직은
OpenAI가 아닌 provider row는 현재 live OpenAI TTS로 보내지 않고 기존 fallback을 사용한다.
dev 런타임 스키마 보강은 기존 DB의 `app.persona_voice_map` 누락도 복구해 seed materializer와
`/voice/ws` 바인딩이 같은 테이블을 사용하게 한다.
스트림 청크마다 립싱크 RMS 힌트(`estimate_chunk_rms`, PCM 디코딩 없이 바이트 에너지 근사)를 동봉한다.
프론트는 마이크 클릭 시 `AudioContext`를 먼저 resume해 재생 권한을 확보하고, TTS Blob을 Web Audio
buffer source로 재생하면서 `AnalyserNode`로 립싱크 RMS를 산출한다. 디코딩 실패 시 `<audio>` 재생으로
fallback한다.
- 키 없으면 명확히 degraded(`is_available()=False`, `VoiceUnavailable`). 라우트가 503/WS close로 변환.
`/voice/ws` WebSocket 캐스케이드(`routes/voice.py`):
```
client: audio_start → [binary chunks] → audio_end
server: ready → state(listening) → state(thinking) → transcript → reply
→ state(speaking) → tts_chunk(메타) + [binary audio] → tts_end → state(idle)
→ state(speaking) → [binary audio] → tts_end → state(idle)
```
- 인증: REST와 동일한 서버측 세션 쿠키를 WebSocket 쿠키에서 복원(`_principal_from_websocket`),
learner만 허용.
@ -361,11 +363,13 @@ GET {ENGINE_URL}/ready|/health — readiness/liveness
```
- `EngineMessage(role, content, cache)``cache`는 L0~L2 cache_control 힌트(게이트웨이가 해석).
- `GenerateRequest(ai_role, messages, tier, model, max_tokens, temperature, structured_schema, session_id, metadata)`
`tier`: `client`(Sonnet/Solar) / `feedback`(Opus) / `fast`(Haiku) 라우팅 힌트.
- `GenerateRequest(ai_role, messages, model, max_tokens, temperature, structured_schema, session_id, metadata)`.
- `session_id`를 넘기면 게이트웨이가 상주 풀을 재사용해 멀티턴 prompt caching 이점을 살린다.
- 장애는 `EngineError`로 전파 → 라우트가 503/SSE error 프레임으로 변환.
- 앱 전역 싱글톤 `engine_client`(lifespan에서 startup/shutdown).
- Node.js 교체 가능성을 위해 wire contract는 `app/contracts/engine_gateway.py`가 소유한다.
현재 FastAPI client와 Python gateway가 같은 `GenerateRequest`/`GenerateResponse`/`Stream*Event`
모델과 SSE `token|done|error` 프레임 helper를 공유한다.
### 2.11 in-memory 스토어 — `app/store.py` (degraded 폴백)
@ -416,7 +420,7 @@ cd apps/api && uvicorn engine_gateway.gateway:app --host 0.0.0.0 --port 9099
- **상주 풀** `/session`, `/session/{sid}/turn`, `/session/{sid}`(DELETE) — 회기 단위 프로세스.
- **stateless 어댑터**(engine_client 계약과 정합):
- `POST /v1/generate``EngineMessage[]``_split_messages()`로 (system_prompt, last_user) 분리
`--append-system-prompt`로 주입, 마지막 user를 stdin content로. structured_schema는
`--system-prompt`로 주입, 마지막 user를 stdin content로. structured_schema는
`_inject_schema()`로 system에 JSON 준수 지시 주입(claude -p는 response_format 미지원이라 차선).
- `POST /v1/stream``turn_stream()`이 assistant 텍스트 델타를 즉시 yield → SSE
`event: token` / `event: done`(provider/model/cost 메타) / `event: error` 프레이밍.
@ -453,7 +457,7 @@ React 19 + Vite. 라우팅은 `apps/web/src/App.tsx`(react-router-dom).
| `/admin` | Admin(운영 홈) | admin |
| `/admin/users` | Admin(사용자 관리) | admin |
| `/admin/access` | Admin(접근 권한) | admin |
| `/admin/tickets` | Admin(운영 티켓 처리 큐: 접수, 우선순위, 상태 변경) | admin |
| `/admin/tickets` | Admin(운영 티켓 처리 큐: 접수, 필터, 카테고리 큐, 우선순위, 수동 상태 변경 감사) | admin |
| `/settings` | Settings | 3역할 공통 |
- `RequireAuth``lib/auth`의 AuthContext로 가드하고, 권한 불일치 시 역할 홈(`roleHomePath`)으로 보낸다.
@ -523,7 +527,10 @@ DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에
- **운영 콘솔** `app.admin_health_event` — 관리자 `/admin/health` 조회 시점의 서비스별 헬스 샘플을
append-only로 남긴다. 이 값은 상시 모니터링 SLA가 아니라 운영 콘솔이 관측한 최근 샘플 이력이다.
`app.support_ticket`은 인증 사용자가 제출한 문제·불만·장애 티켓을 저장하고, 관리자는 `/admin/tickets`에서
우선순위와 처리 상태를 갱신한다. RLS는 티켓 제출자 본인 insert/select와 admin 전체 처리만 허용한다.
상태·카테고리·우선순위·담당그룹·정체·검색 필터와 카테고리 큐를 실제 DB 기준으로 처리한다.
티켓 접수와 관리자 수동 처리 변경은 `audit.audit_log`에 metadata-only로 남기며, 제목/본문 전문은
감사 로그에 복제하지 않는다. 중복 병합, 자동 수정 후보, 이슈/PR 생성은 운영자 승인 전까지 실행하지 않는다.
RLS는 티켓 제출자 본인 insert/select와 admin 전체 처리만 허용한다.
- **메모리 4계층**:
- ① WORKING `app.session_state` — 상태머신 수치 체크포인트(매 턴 UPSERT, openness/ideation CHECK 제약).
- ② EPISODIC 임베딩 `app.turn_embedding` — BGE-M3 dense `vector(1024)` + sparse, HNSW 인덱스.

View file

@ -382,7 +382,7 @@ RAG 임베딩/리랭커 의존성은 기본 이미지에 설치하지 않는다.
# 백엔드 (apps/api)
cd apps\api
python -m pytest app/ -q # 현재 145 pass
python -m pytest engine_gateway\ -q # 현재 9 pass
python -m pytest engine_gateway\ -q # 현재 11 pass
# 웹 (apps/web)
cd apps\web

View file

@ -60,9 +60,9 @@
| ID | 갭 | 현재상태 | 권고 | 근거 |
|---|---|---|---|---|
| **H1** | 계약 평가 KPI(자기효능감·기술숙련도·수련만족도 사전사후) 수집·집계 전무 | `자기효능감/사전사후/수련만족/실험통제군` grep 0건. Phase3 KPI도 report shape만, 계산 코드 0줄. (분석) | 3척도 pre-post 폼·실험/통제군 배정·자동누적 대시보드·추이 시각화·검정 계산 코드. (κ/ICC·환각률은 doc4 미명시 → 평가설계 확정.) | doc4(20명 실험/통제군·단회기 50분·3척도 pre-post) |
| **H2** | 턴별 회기 리뷰 fast-loop 1차 가동·골든셋/2열 UI 잔여 | `make_eval_hook`이 submit/voice 생성 경로에 주입되고, stream은 `_evaluate_stream_turn`으로 fast-loop 평가를 붙인다. 결과는 `feedback_scores`, `alternative_utterance` 등 정규화 테이블에 적재·hydrate된다. | 회기리뷰 UI 좌(축어록 타임라인+비언어)/우(기법·적절성·대안반응·이론) 2열 고도화, 원천 축어록 few-shot 골든셋 적재. | doc2·doc5(골드 포맷) |
| **H2** | 턴별 fast-loop + 라이브 코칭 1차 가동·골든셋/리뷰 2열 UI 잔여 | `make_eval_hook`이 submit/voice 생성 경로에 주입되고, stream은 `_evaluate_stream_turn`으로 fast-loop 평가를 붙인다. 결과는 `feedback_scores`, `alternative_utterance` 등 정규화 테이블에 적재·hydrate된다. 추가로 `app/services/live_coach.py`, `POST/GET /sessions/{id}/live-coach`, `POST /kb/live-coach/source-packs/sync`, `app.live_coach_events`, `data/kb/live_coaching_workbook_0615.json`, `data/kb/live_coaching_sources/*.json`을 연결해 워크북·DSM·공식 지침 요약 기반 코칭 아바타 말풍선·근거 모달·발화별 이력 오버레이와 RAG 증분 색인을 제공한다. | 회기리뷰 UI 좌(축어록 타임라인+비언어)/우(기법·적절성·대안반응·이론) 2열 고도화, 원천 축어록 few-shot 골든셋 적재, 임상팀 확정 루브릭과 source pack 버전·citation 운영정책 보강. | doc2·doc5(골드 포맷) |
| **H3** | 임상팀 콘텐츠 입력 경로(페르소나 저작 CRUD) 2차 구현·임상 검수 잔여 | draft 생성·조회·편집·검수요청 API와 교수 콘솔 JSON 초안 패널은 연결됐다. `persona_repository.py`는 in-code `SEED_PERSONAS`(P1~P3)와 `data/personas/P4.json`~`P7.json``PersonaCard`로 합쳐 `materialize_seed_personas()`와 seed fallback catalog에 포함한다. | JSON 대신 항목형 저작 UI, 루브릭·이론 콘텐츠 외부화, P4~P7 포함 임상팀 최종 검수/서면 evidence 확보. | doc3(R&R)·doc4(페르소나=전문가 산출물) |
| **H4** | PII 마스킹 한국어 이름/기관 NER + 동의 게이트 잔여 | Presidio `language='en'` 고정이라 이름/기관명은 NER 보강이 필요하지만, 한국어 날짜·금액·행정구역 주소 정규식 폴백은 추가됐다. 외부 LLM 호출은 상담 생성(generate/stream)·fast/deep 평가 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency만 적재하도록 연결했고, prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log` 3행 증가를 확인했다. `app_user.consent_at` 기반 학습자 동의 수락/철회 엔드포인트와 회기 시작·voice dev persona 시작 하드게이트를 추가했고, 프론트 세션 시작 전 동의 UI와 E2E 동의 seed를 연결했다. | 한국어 이름/기관 NER 추가, 미성년/guardian 및 법무 검토가 필요한 서명 동의서/개인정보 고지 evidence 확보. 공개 Google OAuth 실제 `/turn` proof는 별도 운영 게이트. | doc1/2/5(실명·날짜·미성년·자살시도 다수)·doc4(IRB·개인정보) |
| **H4** | PII 마스킹 한국어 이름/기관 NER + 온보딩·동의 게이트 잔여 | Presidio `language='en'` 고정이라 이름/기관명은 NER 보강이 필요하지만, 한국어 날짜·금액·행정구역 주소 정규식 폴백은 추가됐다. 외부 LLM 호출은 상담 생성(generate/stream)·fast/deep 평가 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency만 적재하도록 연결했고, prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log` 3행 증가를 확인했다. `app_user`에 이름·소속·학과·학년/직위·연락처·주소/수령지·닉네임·자기소개·아바타 URL·약관/개인정보 동의 버전 필드를 추가했고, 로그인 직후 `/onboarding` 완료 전에는 역할 홈과 learner 회기 시작을 막는다. 아바타 이미지는 `/users/me/avatar`에서 MIME/시그니처/3MB 제한 후 파일 저장소에 두고 URL만 보관한다. 온보딩 저장 시 learner `consent_at`도 함께 세팅하며 auth E2E에서 신규 계정 온보딩→아바타 업로드→학습자 홈 이동을 검증했다. | 한국어 이름/기관 NER 추가, 미성년/guardian 및 법무 검토가 필요한 최종 서명 동의서·개인정보 처리방침·약관 evidence 확보. 공개 Google OAuth 실제 `/turn` proof는 별도 운영 게이트. | doc1/2/5(실명·날짜·미성년·자살시도 다수)·doc4(IRB·개인정보) |
### Medium+ (6) — M1~M3, X1~X2, L1
@ -88,13 +88,13 @@
- **C1 2차 완료**: `caseWorksheet` 응답 구조, 리뷰 화면 편집 UI, `app.case_worksheet` 저장/재조회 경로. 후속은 임상 루브릭·AI 추출/채점·교수자 검수.
- **C2 1차 완료**: 위기 신호는 엔진 전 중단, 109 안내, `safety_events` 적재, 교수자 알림 큐, `ideation_observed` 전달까지 배선했다. 후속은 임상 스크립트·서약 문안·감점 루브릭.
- **C3 1차 완료**: `theory_mode`가 세션·평가·생성 프롬프트까지 흐른다. 후속은 임상팀 CBT 체인·이론부합 루브릭·명시적 선택 UI.
- **H2 1차 완료**: `make_eval_hook`과 stream 평가가 턴 파이프라인에 붙고 정규화 테이블로 적재·복원된다. 대안발화도 `app.alternative_utterance`로 정규화한다. 후속은 2열 리뷰 UI·골든셋.
- **H2 1차+라이브 코칭 완료**: `make_eval_hook`과 stream 평가가 턴 파이프라인에 붙고 정규화 테이블로 적재·복원된다. 대안발화도 `app.alternative_utterance`로 정규화한다. 라이브 코칭은 0615 워크북·DSM·공식 지침 요약/RAG 근거로 코칭 아바타 말풍선·근거 모달·발화별 이력 오버레이까지 연결했다. `POST /kb/live-coach/source-packs/sync`로 같은 source pack을 evaluator 전용 RAG에도 증분 색인한다. 후속은 2열 리뷰 UI·골든셋·임상팀 루브릭·source pack 버전 운영.
- **H3 2차 완료**: 페르소나 저작 CRUD(draft→review)와 P4~P7 저장소 JSON 로드 경로가 붙었다. 후속은 항목형 저작 UI, 루브릭·이론 콘텐츠 외부화, 임상팀 최종 검수 evidence.
- **H4(부분)·X1·X2**: 마스킹 한국어 정규식 보강, 외부 LLM 호출 metadata-only `audit.llm_call_log` 적재 경로, 학습자 동의 수락/철회/회기 시작 하드게이트 골격은 완료했다. 한국어 이름/기관 NER와 guardian/legal 서명 evidence는 후속이다. dry-run JSONL export·PII scan·IAA 계산 1차도 완료했고 `ds.*` write는 `--write-dataset` 명시 시에만 수행한다. X2 비용 관측·예산 경고와 evaluator fast/deep 모델 override 1차는 완료했고 semantic cache·장기 한도 정책은 후속. M1/M2도 1차 구현 완료, provider 기반 비언어 감지와 case_digest/pinned_fact 실적재는 후속.
### B. 소유자 결정 / 외부(임상팀·기관) 의존
- **임상팀(구훈정·어유경) 산출물**: C1 채점 루브릭·항목 확정, C3 CBT 이론 콘텐츠·프롬프트 체인, C2 위기 스크립트·서약 문안, H2 골든셋 코딩. → 코드는 구조를 선제 구축하되 임상 문안과 평가기준은 외부 정의로 받는다.
- **임상팀(구훈정·어유경) 산출물**: C1 채점 루브릭·항목 확정, C3 CBT 이론 콘텐츠·프롬프트 체인, C2 위기 스크립트·서약 문안, H2 골든셋 코딩·라이브 코칭 루브릭 검수. DSM/공식 지침/논문은 사용 허가 확인 기준으로 KB 확장 가능하며, 각 source pack은 version/citation/임상 검수 상태를 남긴다. → 코드는 구조를 선제 구축하되 임상 문안과 평가기준은 외부 정의로 받는다.
- **소유자 평가설계 결정**: H1 실험/통제군 배정·3척도 문항·50분 흐름. κ/ICC·환각률 목표는 doc4 미명시 → 평가설계 문서 확정.
- **기관(한신 IT) 의존**: M3 SSO IdP 프로토콜·test tenant·claim 스키마, SAML 인증서, deprovisioning 거버넌스 증거.
- **거버넌스 결정**: L1 스택 정합성 처리 방향, 저작권 등재 문서화 수준, IP 협의.
@ -135,7 +135,7 @@ rg -n "SEED_PERSONAS|load_file_personas|built_in_personas|materialize_seed_perso
```powershell
# 백엔드 (작업 디렉터리 apps/api)
python -m pytest app/ -q # 현재 118 pass
python -m pytest engine_gateway/ -q # 현재 7 pass
python -m pytest engine_gateway/ -q # 현재 11 pass
# 프론트 (작업 디렉터리 apps/web)
npm run typecheck

View file

@ -15,12 +15,12 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
| 검증 | 작업 디렉터리 | 명령 | DB | API(8000) | 웹(5173) | 엔진GW(9099) | 브라우저 | 현재 통과 |
|---|---|---|---|---|---|---|---|---|
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 113 pass |
| 엔진 게이트웨이 테스트 | `apps/api` | `python -m pytest engine_gateway/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 7 pass |
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 145 pass |
| 엔진 게이트웨이 테스트 | `apps/api` | `python -m pytest engine_gateway/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 11 pass |
| API 타입 생성 체크 | `apps/web` | `npm run check:api-types` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
| 웹 타입체크 | `apps/web` | `npm run typecheck` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | |
| 웹 빌드 | `apps/web` | `npm run build` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | |
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 102 tests / 17 files |
| 웹 타입체크 | `apps/web` | `npm run typecheck` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
| 웹 빌드 | `apps/web` | `npm run build` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 113 tests / 17 files |
핵심 원칙: **단위 테스트(pytest)와 타입체크/빌드는 외부 서비스 없이 단독 실행된다.**
**E2E만 풀스택(DB+API+웹+브라우저)을 요구한다.** 아래 각 절에서 근거와 절차를 설명한다.
@ -42,15 +42,15 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
```sh
# apps/api
python -m pytest app/ -q # 앱 단위 테스트 (현재 113 pass)
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트 (현재 7 pass)
python -m pytest app/ -q # 앱 단위 테스트 (현재 145 pass)
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트 (현재 11 pass)
```
수집만 빠르게 확인하려면:
```sh
python -m pytest app/ --collect-only -q # → "113 tests collected"
python -m pytest engine_gateway/ --collect-only -q # → "7 tests collected"
python -m pytest app/ --collect-only -q # → "145 tests collected"
python -m pytest engine_gateway/ --collect-only -q # → "9 tests collected"
```
> 참고: 실행 중 `PendingDeprecationWarning: Please use 'import python_multipart'`
@ -62,11 +62,13 @@ python -m pytest engine_gateway/ --collect-only -q # → "7 tests collected"
|---|---|
| `app/test_runtime_policy.py` | 런타임 정책(공개/dev 모드, dev-login 가드) |
| `app/test_session_turn_persistence.py` | 세션 턴 영속화(DB/인메모리 양쪽) |
| `app/test_session_share.py` | 회기 리뷰 공유 URL, sanitized 공개 payload, 토큰 폐기 |
| `app/test_evaluation_persistence.py` | fast-loop 평가 정규화 적재/복원 매핑 |
| `app/test_orchestrator_masking.py` | 오케스트레이터 PII 마스킹 게이트 |
| `app/test_state_machine_resistance.py` | 저항엔진 상태머신(openness 전이) |
| `app/test_rbac_idor.py` | RBAC / IDOR 권한 경계 |
| `app/test_auth_providers.py` | 인증 프로바이더(SSO/allowlist) |
| `app/test_admin_ops.py` | 관리자 콘솔 운영 저장 모델(헬스·티켓) |
| `app/test_persona_review.py` | 페르소나 리뷰 워크플로 |
| `app/test_voice_service.py` | 음성 캐스케이드 서비스(STT/TTS) |
| `app/test_voice_ws.py` | 음성 WebSocket 경계 |
@ -75,7 +77,7 @@ python -m pytest engine_gateway/ --collect-only -q # → "7 tests collected"
| 파일 | 검증 영역 |
|---|---|
| `engine_gateway/test_gateway_model.py` | 게이트웨이 모델 선택·요청/응답 계약(ENGINE_MODE별) |
| `engine_gateway/test_gateway_model.py` | 게이트웨이 모델 선택·공유 engine contract·SSE 요청/응답 계약(ENGINE_MODE별) |
---
@ -228,10 +230,13 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
### 3.6 실측 테스트 개수 (현재)
`npx playwright test --list` 기준 **총 102 tests / 17 files**.
`npx playwright test --list` 기준 **총 113 tests / 17 files**.
- **병렬 시나리오**: 88 tests (desktop + mobile)
- **`@single-run` 직렬 시나리오**: 14 tests (DB 영속화·세션 MVP·음성 성공경로 등)
- **병렬 시나리오**: 96 tests (desktop + mobile)
- **`@single-run` 직렬 시나리오**: 17 tests (DB 영속화·세션 MVP·음성 성공경로 등)
- `e2e/voice-success.spec.ts`는 직접 `/voice/ws` 캐스케이드와 Session 마이크 UI를 함께 검증하며,
브라우저 `<audio>.play()`가 차단된 조건에서도 Web Audio buffer source 재생이 시작되는지 확인한다.
- 2026-06-27 최종 로컬 풀스택 검증: `PLAYWRIGHT_PORT=5174 npm run e2e` **113 passed**.
레이아웃·시각 회귀 게이트(핵심 합격선):
@ -239,12 +244,13 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
|---|---|---|---|
| 세션 레이아웃 | `e2e/session-layout.spec.ts` | 4 테스트 × (desktop+mobile) | **8 / 8** |
| 시각 레이아웃 게이트 | `e2e/layout-visual-gate.spec.ts` | `@single-run`, 7개 화면 × 7개 폭 검사 | **7 / 7** |
| 레이아웃 포커스(재설계 화면) | `session-layout`·`session-review`·`admin`·`learner`·`settings`·`teacher`, `@single-run` 제외 | desktop+mobile 병렬 | **54** |
| 레이아웃 포커스(재설계 화면) | `session-layout`·`session-review`·`admin`·`learner`·`settings`·`teacher`, `@single-run` 제외 | desktop+mobile 병렬 | **58** |
> `layout-visual-gate`는 7개 폭(390/720/861/900/1024/1280/1440)에서 가로 오버플로·잘린
> 컨트롤을 검사하고 전체 페이지 스크린샷을 `node_modules/.tmp/layout-gate/`에 남긴다.
> `session-layout`은 회기 전/활성 화면이 뷰포트를 벗어나지 않는지, 우측 패널이 코어 영역을
> 침범하지 않는지, 스트림 실패 시 미저장 전사가 남지 않는지를 검증한다.
> 침범하지 않는지, 시작 후 실제 `session_id` URL에서 새로고침해도 활성 회기 상세가 유지되는지,
> 스트림 실패 시 미저장 전사가 남지 않는지를 검증한다.
### 3.7 공개 인증 스모크(선택)