# Vignette 백로그 — 2026-06-26 이 세션(레이아웃 시각 수용 마감 + live 운영 증거 라운드) 이후 남은 항목을 한곳에 모은다. 대시보드(`docs/dev_dashboard.html`)의 상태와 일치한다. 운영 원칙상 가짜 증거로 DONE 표기하지 않으므로, 실증/외부 의존/소유자 결정이 필요한 항목은 여기서 추적한다. 분류: **B1 비차단 폴리시** · **B2 환경 제약(증거 생산 불가)** · **B3 소유자 결정** · **B4 외부 거버넌스** > **B0. 원천문서 갭 분석 (2026-06-26 추가)** — 한신대 산학협력 원천문서 5종 정독으로 도출한 "부족한 부분"(critical 3 / high 4 / medium+ 6)은 **SSOT 대시보드** `docs/dev_dashboard.html` "원천문서 갭 분석" 섹션과 상세 `docs/ops/source-docs-gap-analysis-2026-06-26.md`에서 추적한다. C1 사례개념화 산출물, C2 위기개입 프로토콜, C3 이론모드 스레딩은 1차 구조를 만들었다. 콘텐츠 정의는 임상팀(구훈정·어유경) 소유라 코드는 구조를 선제 구축하되 임상 문안과 평가기준은 외부 정의로 받는다. --- ## B1. 비차단 코스메틱 폴리시 - [x] **공용 셸 사이드바 구분선 전체 높이** — (2026-06-26 처리) `.vg-nav border-right`(sticky·뷰포트 높이)를 제거하고, `.vg-shell__body` 컨테이너 배경 하어라인(`linear-gradient` 1px×100%, `--nav-cur`로 그리드·구분선 폭 동기화)으로 본문 그리드 전체 높이를 따라가는 세로 구분선을 그린다. 1024 축소폭은 `--nav-cur: var(--nav-w-collapsed)`로, 720 이하(상단 가로바 전환)는 `background-image:none`으로 정합. `components/shell/shell.css`. **검증: `npm run typecheck` PASS + `vite build` PASS.** 시각 회귀 게이트(gate 7/7 + 포커스 E2E + session-layout 8/8)는 web+api+DB 스택(Docker 미기동·secrets 부재)으로 이 환경 미실행 — 스택 가용 시 1회 시각 확인 권장. - [x] **세션 종료 UX와 theme SSOT** — (2026-06-27 처리) 오발동과 모바일 조작 불안을 줄이기 위해 세션 하단의 드래그형 `SlideToEnd`를 명시 확인 다이얼로그로 교체했다. Topbar/Settings의 `vignette.theme` 읽기·저장·`data-theme` 적용은 `apps/web/src/lib/theme.ts` 단일 경로로 통합했고, 저장값이 없으면 dark 기본값을 부팅 전에 적용한다. **검증: `npm run typecheck`, `npm run build`, `npx playwright test e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts` 15 passed.** - [ ] **빈상태 컬럼 높이 여백** — session-review 1280 masonry 우측 컬럼, session-prestart 우측 '진행 초점' 컬럼, learner-home 1440 하단 등 데이터 0건 빈상태에서만 두드러지는 컬럼 높이 불균형/여백. 실데이터면 채워진다. **(미착수, 의도적)** 0건 전용 시각 균형이라 러닝 앱에서의 시각 반복 없이 블라인드 수정 시 악화 위험 → 스택 가용 시 시각 확인하며 처리. 빈상태 전용 균형 레이아웃 채택 자체가 별도 폴리시 결정. - [x] **learner-home 로딩 스켈레톤 밀도(390)** — (2026-06-26 처리) '연습 대상' 로딩 스켈레톤을 빈 div 단일 셰이머에서 실제 카드 구조(마크 박스 + 이름/메타/요약 2줄 플레이스홀더)를 모사하도록 밀도 보강. `LearnerHome.tsx` (`.lh-skel__box`/`.lh-skel__line*`), reduced-motion 분기 동반 갱신. **검증: `npm run typecheck` PASS + `vite build` PASS.** 검증 기준: 변경 후 `npm run typecheck`, `e2e/layout-visual-gate.spec.ts`(7/7), 레이아웃 포커스 E2E(54), `e2e/session-layout.spec.ts`(8/8) 무회귀. (E2E 게이트는 web+api+DB 스택 필요 — 현재 워크스테이션은 Docker 미기동·API secrets 부재로 미실행. 코드 변경은 typecheck+vite build로 1차 검증함.) --- ## B2. 환경 제약 — 이 워크스테이션에서 증거 생산 불가 - [ ] **`vnet.18ka.net` 공개 live 전환** — 코드/스크립트는 `vnet.18ka.net`·`api-vnet.18ka.net`을 허용하도록 갱신 완료. Tailnet 주소 `https://alpaca-home.taile93291.ts.net`은 dev-login까지 검증됐고, public OAuth redirect가 로컬/Tailnet 세션으로 붙지 않는 혼선을 막기 위해 Tailnet Google 버튼과 직접 Google 시작 URL은 `local_oauth_unavailable`로 차단한다. 공개 vnet은 현재 (1) `vnet.18ka.net`/`api-vnet.18ka.net` DNS 부재, (2) cloudflared 현재 인증 컨텍스트가 `18ka.net` zone이 아니라 `chanpaca.net` zone에 route dns를 생성, (3) Google Console redirect URI `https://api-vnet.18ka.net/auth/callback` 등록 필요 때문에 live 완료 아님. 현재 prod API 8001 자체는 `db:true`, `engine:true`로 건강하다. 상세: `docs/ops/tailscale-vnet-runtime-2026-06-27.md`. - [ ] **공개 Google OAuth 실제 `/turn` proof** — 로그인 가능한 계정으로 `storageState` 캡처 후 `E2E_PUBLIC_AUTH=1` + `chromium-public-auth` 1회 통과 필요. (소유자 지시로 이번엔 보류.) - [ ] **음성 캐스케이드 live** — 실제 Deepgram STT / OpenAI TTS provider 키, 물리 마이크, 공개 WSS 50분 양방향 실측. provider 키/하드웨어 부재로 미실행(백엔드 계약·UI synthetic 경로는 단위/E2E로 증명됨). - [ ] **claude_cli ↔ Messages API 폴백 동일성** — `ANTHROPIC_API_KEY`가 있어야 Messages API 경로를 돌려 동일성 검증 가능. claude_cli 경로는 게이트웨이 probe로 live 실측 완료. - [ ] **저항엔진 openness 곡선 DB 실증** — fast-loop 평가의 `client_state_read` → `app.turn_client_state` 적재 경로는 추가됨. 다만 dev DB 실데이터/다수 실턴(real LLM) 기반 openness 곡선 증거는 아직 필요. 현재는 `app.test_state_machine_resistance` 단위테스트와 `app.test_evaluation_persistence` 매핑 테스트로 1차 증명. - [ ] **재부팅 후 watchdog smoke** — `watch-public-runtime.ps1` + Scheduled Task가 재부팅 후 엔진/API/터널을 복구하고 public `/turn`이 통과하는지 실측. 재부팅 불가로 미실행(parser/ check-only 경로는 확인됨). 2026-06-27 점검에서 아직 DNS가 없는 `api-vnet.18ka.net`은 기본 watchdog 검사에서 제외했고, 개통 후 `-AdditionalPublicHealthUrls`로 명시 추가하도록 runbook을 보강했다. - [ ] **상주 엔진풀 RSS 실측** — probe로 TTFT/세션재사용/cost는 live 확인. 프로세스 RSS(메모리)는 별도 OS 계측 항목으로 남김. --- ## B3. 소유자(윤찬) 결정 필요 — 단독 결정 시 월권 > 결정되면 대시보드 `결정 필요` 탭과 관련 Phase 항목을 갱신한다. - [ ] **s2s 2차 PoC 채택 여부** — 캐스케이드만 / s2s 2차 PoC 유지. 영향: 음성 아키텍처·전송 비용·로그 보존. 기준표 `docs/decisions/voice-s2s-poc.md`. - [ ] **서비스용 페르소나 음성 모델·권리 정책** — (2026-06-27) P1 서연 음성 아트 PoC는 Higgs v3 무참조 synthetic seed 방식으로 생성(`docs/voice-art/p1-seoyeon-higgs-v3-20260627/`). 실존 인물/성우 reference 사용 금지 확정. `VIGNETTE_VOICE_POC_SAMPLE_TTS=true` 개발 플래그로 `/voice/ws` P1 sample TTS provider에 연결했지만, 서비스 배포 전 선택 필요: Higgs 상업 라이선스 확보 / 동의 받은 reference voice만 사용 / 배포 가능한 대체 TTS provider 채택. - [ ] **fast-loop 외부 API 허용** — 로컬 전용 / 외부 Haiku·Solar 폴백 허용. 영향: 데이터주권·레이턴시·장애 대응. - [ ] **재귀학습 fine-tuning 범위** — few-shot 자동갱신만 / fine-tuning 포함. 영향: 동의서·데이터셋 품질·운영 리스크. - [ ] **자유연습 기본값** — 기본 ON(+피드백 권장 배지+교수자 토글) / 교수자 승인 후 ON. 영향: 학습자 자율성·평가 품질·안전 정책. - [ ] **추가 축어록 수급 규모·일정** — 목표 N건/일정 확정. 영향: 평가 타당도·페르소나 다양성·Phase 3 측정력. --- ## B4. 외부 거버넌스 — 한신대/데이터 steward 서면 증거 - [ ] **한신대 데이터/SSO 거버넌스 게이트** — SSO claim mapping, 추가 축어록 수급, 미성년 원본 활용동의, 개인정보 처리방침. 로컬 문서 골격 `docs/ops/hanshin-data-governance-gate.md` 준비됨, 외부 서면 증거 필요. (소유자 지시로 이번엔 보류.) --- ## Phase 3 파일럿 게이트 (실참여자 필요) - [ ] 20명 교육용 파일럿 운영 / 효과성·KPI 측정(SUS·자기효능감·κ/ICC·환각률) / 재귀학습 데이터셋 approved 산출 / 개인정보·동의 감사. 문서/checker와 dry-run exporter는 준비됨(`docs/phase3/*`, `scripts/check-phase3-artifacts.py`, `scripts/export-recursive-dataset.py`). 2026-06-27 checker 강화로 CSV enum, KPI metric required fields, approved export의 PII pass·κ/ICC·withdrawn exclusion·consent scope·file sha256 false-positive 차단을 추가했다. 실제 approved evidence는 파일럿·steward/legal 검수·IAA 통과 시점. --- ## 이번 세션에 닫은 것(참고) - 레이아웃 시각 수용: 게이트 7/7 + 적대적 재검수 7/7 accept + 코스메틱 폴리시(공용 셸 1건 제외) — DONE. - live 운영 증거: engine config 운영값, 상주 엔진풀 probe(TTFT/cost/세션재사용), Postgres RLS/audit smoke 5 checks PASS, turn cost telemetry(app.turns 13행) — DONE. - P1 서연 음성 아트 PoC: Higgs v3 무참조 synthetic seed + 5개 정서/속도 변주(mp3/wav) 생성. 실존 reference voice 미사용. `/voice/ws`에는 dev-only sample TTS provider로 연결했고 live text_turn smoke에서 `p1-sample-poc` binary 20 chunks/78,573 bytes 수신 확인. 이 provider는 TTS override만 담당한다(마이크/STT는 OpenAI 키 필요). 프로덕션 탑재는 라이선스/권리 결정 전 금지. - 부수: RLS smoke 스크립트 cleanup RLS 컨텍스트 버그 수정, `vignette_app`(NOBYPASSRLS) dev 역할 프로비저닝. - 평가 SSOP 2차: `TurnRecord.evaluation`을 `app.feedback_scores`/`turn_technique`/`turn_client_state`/`supervisor_comment`/`alternative_utterance`로 정규화 적재·복원하고, submit/stream/voice 저장 경로를 `app.turn_runtime`으로 공통화했다. 원시 평가 테이블은 evaluator/admin RLS로 제한하고 서버가 evaluator 컨텍스트로 적재한다. `app.turns` insert는 `RETURNING id` 확인으로 silent success 제거. **검증: `pytest app/test_evaluation_persistence.py -q` 6 passed, 관련 회귀 45 passed, `pytest app/` 119 passed, `pytest engine_gateway/` 7 passed.** - 계약 SSOT 3차: FastAPI OpenAPI를 `apps/web/src/lib/api.gen.ts`로 생성하는 `npm run generate:api-types`와 stale 체크용 `npm run check:api-types`를 유지한다. `TurnResponse`/`SessionEndResponse`에 이어 `MeResponse`, `AuthConfigResponse`, `ConsentResponse`, `PersonaSummary`, `PersonaReviewSummary`, admin/user/engine DTO, review leaf DTO, `CrisisResourceResponse`, teacher safety/growth leaf DTO, `LearnerSessionsResponse`, `SessionReviewResponse`, worksheet aggregate, `TeacherDashboardResponse`, `TeacherSessionSummary`, `TeacherLearnerGrowth`를 생성 타입 alias로 전환했다. generated optional/default 차이는 UI form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. CI 연결과 SessionDetail/SessionStart 계열의 stage enum 정리는 후속. **검증: `npm run check:api-types`, `npm run typecheck`, `npm run build`, `learner.spec.ts + session-review.spec.ts + teacher.spec.ts + layout-visual-gate.spec.ts` 32 passed.** - 위기게이트 109 1차: 실제 자해·자살 신호는 LLM/엔진 호출 전 중단하고 `crisis_resource(109)`와 `conversation_stopped`를 REST/SSE/voice 응답에 싣는다. `app.safety_events` detail 적재와 교수자 대시보드 안전 알림 큐까지 연결했다. 안전 이벤트 DB insert payload도 회귀화했다. 임상 문안, 실시간 push/메일, 지역 자원 확장은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 20 passed, `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed.** - 교수자 성장 추적 1차: `teacher_dashboard`가 정규화된 턴 평가를 기반으로 학습자별 적절성·라포·기술 사용 추이를 집계하고, 교수 콘솔에 최근 회기/항목별 성장 카드와 안전 알림 큐를 함께 표시한다. **검증: `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed, `pytest app/` 119 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`.** - 페르소나 저작 CRUD 1차: teacher/admin 전용 `POST/GET/PUT /personas/drafts` 경로를 추가해 draft 생성·조회·편집·검수요청을 열고, 교수 콘솔에 JSON 초안 작성/불러오기/수정 UI를 연결했다. 승인된 카드만 학습자 catalog에 노출하는 기존 경계는 유지한다. P4~P7 실제 임상 콘텐츠 입력은 후속. **검증: `pytest app/test_persona_review.py -q` 19 passed, `pytest app/` 119 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`.** - M1 비언어 이벤트 1차: voice learner turn에 이미 저장하던 `audio_ref`/`silence_ms`/`speech_rate`/`barge_in`을 리뷰 API `nonverbal` 이벤트로 노출하고, 회기 리뷰 축어록에 침묵·발화 속도·끼어듦·음성 입력 칩을 표시했다. 한숨·울음 감지와 live 마이크/STT/TTS 실측은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 17 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `session-review.spec.ts` 1 passed.** - M2 다회기 케이스 아크 1차: DB 세션 생성 시 `(persona_id, learner_id)` 기준 `case_profile`을 upsert하고, `session_no`를 트랜잭션 안에서 원자 증가시키며, `InProcSession.case_id`가 매회 새 `runtime_case_id`가 아니라 안정 `case_id`를 가리키게 수정했다. 시작/submit/stream/voice 경로는 직전 `session_summary` 기반 seed recall을 사용한다. `case_digest`/`pinned_fact` 실적재, episodic embedding writer, trajectory 갱신은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 18 passed, `pytest app/ -q` 119 passed, `npm run check:api-types`.** - C1 사례개념화 워크시트 1차: `SessionReviewResponse.caseWorksheet`와 리뷰 화면 read-only 카드가 탐색 11항목·호소 5영역·인지삼제/감정·보호/방해·생물심리사회 목표 초안을 축어록 근거 기반으로 제공한다. 편집·DB 저장, 임상 루브릭, AI 추출/채점, 교수자 검수는 후속. **검증: `pytest app/test_session_turn_persistence.py -q` 15 passed, `pytest app/ -q` 119 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `session-review.spec.ts` 1 passed.** - C3 이론모드 1차: `theory_mode`가 `TurnContext`/sessions/voice/evaluator뿐 아니라 `build_turn_messages`의 엔진 메시지까지 전달된다. 프론트는 `persona.theory_target` 기준으로 시작해 `humanistic` 하드코딩을 제거했다. CBT 체인·이론부합 루브릭·명시적 선택 UI는 후속. **검증: `pytest app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q` 20 passed, `pytest app/ -q` 119 passed.** - M3 인증 claim 1차: Google/SAML/dev-login이 `AUTH_EMAIL_COHORT_MAP`·`AUTH_DOMAIN_COHORT_MAP` 및 SAML cohort claim을 `cohort_ids`로 전달하고, DB `app_user.external_id`는 provider subject(`google:`/`saml:`/`dev:`) 기반으로 저장한다. 운영 SAML 서명검증, 기관 claim schema/test tenant, deprovisioning audit은 후속. **검증: `pytest app/test_auth_providers.py -q` 22 passed, `pytest app/ -q` 119 passed.** - X2 예산 경고/저비용 평가 라우팅 1차: `ADMIN_USAGE_BUDGET_USD` 설정값을 기준으로 `GET /admin/usage`가 `budget.status=disabled|ok|warn|exceeded`, 사용률, 잔여 예산을 반환하고 관리자 콘솔이 예산 상태 배너를 표시한다. 80% 이상 warn, 100% 이상 exceeded. `EVALUATOR_FAST_MODEL`/`EVALUATOR_DEEP_MODEL` 설정 시 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달하고, 비워두면 기존 gateway default 라우팅을 유지한다. semantic cache·장기 한도 정책은 후속. **검증: `pytest app/test_evaluator_model_routing.py app/test_runtime_policy.py -q` 23 passed, `pytest app/ -q` 122 passed, `npm run check:api-types`, `npm run typecheck`.** - H4 LLM call audit 1차: 상담 생성(generate/stream)·fast-loop 평가·deep-loop 평가의 외부 LLM 호출 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency metadata만 적재한다. prompt/completion 본문은 저장하지 않고 감사 실패는 상담 루프를 막지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log`가 8→11로 3행 증가했다(session `2460d56c-a9cb-4a40-a175-9575d510a5e9`). 공개 Google OAuth 실제 `/turn` proof는 별도 B2 항목에 남긴다. **검증: `pytest app/test_orchestrator_masking.py app/test_evaluation_persistence.py app/test_session_turn_persistence.py -q` 27 passed, `pytest app/ -q` 119 passed.** - H4 동의 게이트 1차: `app_user.consent_at`를 런타임 테이블 readiness와 `SessionUser`/`Principal`에 포함하고, learner 전용 `POST /auth/consent`·`DELETE /auth/consent`를 추가했다. 동의가 없는 learner는 `POST /sessions`와 voice dev persona 시작이 `consent_required`로 막히고, 프론트 세션 시작 전 동의 체크 UI가 먼저 저장하도록 안내한다. E2E dev-login helper는 세션 테스트 전에 동의를 seed한다. 한국어 이름/기관 NER, guardian/legal 서명 동의서 evidence, 공개 Google OAuth 실제 `/turn` proof는 후속. **검증: `pytest app/ -q` 125 passed, `pytest app/test_auth_providers.py app/test_session_turn_persistence.py app/test_persona_review.py app/test_voice_ws.py -q` 64 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, local `/sessions` no-consent 403 + `/auth/consent` 200.** - RAG warm 동시성 안정화: layout/session E2E가 여러 세션을 빠르게 만들 때 BGE-M3 embedder가 동시에 여러 번 지연 로드되며 `tqdm` lock 예외와 API health/dev-login timeout이 반복됐다. `rag.py`에서 embedder load/encode를 process-wide `RLock`으로 직렬화하고, `sessions.py`의 `_warm_rag_caches`를 semaphore 1개로 제한해 warm task가 상담 요청 경로를 막지 않도록 했다. **검증: `pytest app/ -q` 125 passed, `npx playwright test e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts --project=chromium-single-run --project=chromium-desktop --workers=1` 11 passed.** - 공개/로컬/Tailnet 로그인 복구: public API 530 원인은 prod에서 개발 전용 `VIGNETTE_VOICE_POC_SAMPLE_TTS=true`가 fail-close된 것과 DB `app.admin_engine_config` 기본 행 부재였다. 2026-06-27 모바일 502는 cloudflared 로그의 `127.0.0.1:8001` origin refused와 일치했다. `scripts/start-public-runtime.ps1`는 public prod 기동 시 샘플 TTS를 강제로 끄고, 운영 DB에는 `claude_cli`/`127.0.0.1:9099` engine config 행을 복구했다. 추가로 dev/Tailnet에서는 public OAuth callback이 로컬/Tailnet 세션으로 붙지 않으므로 Google 버튼과 직접 시작 URL을 `local_oauth_unavailable`로 차단하고 dev-login만 사용한다. public OAuth state는 HttpOnly 쿠키에 묶인 HMAC 서명 토큰으로 복구해 API 재시작 뒤 콜백이 `invalid_state`로 떨어지지 않는다. callback 실패는 비밀값 없는 reason/status로 남기고, 로그인 화면도 token/state/provider/identity 실패 메시지와 reason code를 분리한다. 2026-06-27 18:25 KST에 stale Vite `allowedHosts`로 생긴 Tailnet 403을 `scripts/start-tailscale-runtime.ps1` 재기동으로 복구했다. **검증: `https://vignette.chanpaca.net/login` 200, `https://api-vignette.chanpaca.net/health` prod/db/engine true, public Google auth redirect 302 + HttpOnly state cookie, provider callback error → `/login?oauth=access_denied`, `https://alpaca-home.taile93291.ts.net/login` 200 + `/api/health` dev/db/engine true + `/api/auth/config` 200, local/Tailnet Google direct는 `local_oauth_unavailable`, local auth E2E 7 passed, Tailnet auth/dev-login E2E 2 passed.** - Docker dev DB 안전장치: `scripts/dev-up.ps1`가 `vignette-dev-db` healthcheck/pg_isready/role safety를 점검하고, 새 컨테이너 생성 시 `vignette_owner`와 앱 role을 분리해 healthcheck를 붙인다. `infra\.env` compose 필수값 누락도 사전 경고한다. `scripts/dev-down.ps1`은 기본 DB 보존, `-Db` 지정 시 컨테이너 중지로 정리했다. **검증: 현재 Docker DB accepting connections, `vignette_app:false:false`, 기존 컨테이너 healthcheck 없음. 추가 확인 결과 기존 DB는 `POSTGRES_USER=vignette` 기반이고 API startup DDL이 owner 권한을 요구해 런타임 app-role 전환은 마이그레이션 owner/런타임 role 분리 후 진행해야 한다.**