Vignette 커맨드센터팀장 리뷰 · dev status
내 차례 0 2026-06-30 KST
project pulse

지금 위치: Phase 0 제품 데모는 public /turn proof가 핵심 블로커.
Phase 1 MVP 코어 루프는 로컬 DB-backed 실증 완료.

코어(저항·반응 조절, 상태머신, RAG, 이론모드, 가드레일, 평가 JSON Schema)는 잘 정렬됐고, 학습자 홈·교수자 리뷰·공개 웹 로그인·공개 API/Tailnet 기본 스모크도 통과했다. 2026-06-30 레드팀 중 public API Cloudflare 530/error code 1033을 발견했지만, Docker Desktop/DB 재기동 후 public health 200과 비인증 /personas 401로 복구를 확인했다. 단, 공개 Google OAuth 실제 /turn, vnet DNS/zone/OAuth redirect, 실배포 infra/.env, 기관 결정, 물리 마이크·public WSS 같은 게이트는 별도로 남아 있다. 이 판은 검증된 내부 완료외부 증거가 필요한 항목을 분리해서 보여준다.

Full E2E 기준선 158 Node conformance OK · gateway 27 · backend 120 Layout 시각게이트 9/9 pass Tailnet 이전 확인 OK Public API recovered OK 배포지 secrets BLOCK critical 3 1차 완료·gate Decision P0 조건부

당신 차례 — 이걸 풀어주면 진행이 열린다

람다가 단독으로 못 정하거나(월권), 실제 계정·하드웨어·외부 합의가 필요한 항목. 각 카드는 무엇을 막는지 · 무엇이 필요한지 · 왜 당신인지를 담았다. 팀장이 우선순위를 골라 하나씩 풀어주면 된다.

🚧 GO를 막는 블로커 0
블로커Phase 0 마감 · P1 진입

공개 Google OAuth 실제 /turn 증명

필요: 로그인 가능한 계정으로 storageState 캡처 → public-auth-turn E2E 1회 통과.

왜 당신인가: 실제 Google 계정 인터랙티브 로그인이라 자동화 불가. (owner 지시로 현재 보류 상태)

증거: public-auth.json + public /turn 성공 로그
게이트Phase 3 파일럿 · 데이터

한신대 데이터/SSO 거버넌스

필요: SSO claim 스키마, 추가 축어록 수급, 미성년 원본 활용동의, 개인정보 처리방침에 대한 한신대/데이터 steward의 written evidence.

왜 당신인가: 기관 공문·협의 영역. 로컬 문서 골격은 준비됨(hanshin-data-governance-gate.md).

🧭 소유자 결정 (DECIDE) 0
결정됨아키텍처·일정

백엔드 언어 방향

소유자 결정: 장기 교체 대상과 신규 분리 서비스는 Node.js 우선. 단, 현재 FastAPI 전면 재작성은 일정 리스크라 계약 우선 strangler 방식으로 간다.

영향: 팀 역량은 Node로 맞추되, 납품 핵심 경로는 기존 Python 동작을 보존하며 교체 가능한 경계부터 만든다.

결정됨Phase 3 측정

평가설계 (H1: 실험/통제군·3척도·κ/ICC)

소유자 결정(2026-06-30): 평가자 신뢰도 목표 κ≥0.70 & ICC≥0.75, 환각률 ≤3%, 통계검정은 독립표본 t-검정(α=0.05), 20명을 실험/통제군으로 무작위 배정, 단회기 50분·3척도 pre-post는 doc4대로 유지. 설계 파라미터는 확정됐고, 실제 20명 파일럿 evidence·통계해석은 별도 GATE로 남는다.

영향: 평가 타당도, Phase 3 측정력. 설계는 확정, 효과성 판정은 파일럿 데이터 이후.

결정됨음성 아키텍처

s2s 2차 PoC 채택 여부

소유자 결정(2026-06-30): 캐스케이드 1차를 유지하면서 s2s 2차 PoC를 계속 연다. voice-s2s-poc.md 기준표(transcript·safety·audit·latency·failure·privacy)로 비교 리포트를 만든 뒤 최종 채택/폐기를 다시 판단한다.

방향: s2s 2차 PoC 유지 → 비교 PoC 구현·실측이 후속

결정됨페르소나 음성

서비스용 음성 모델·권리 정책

소유자 결정(2026-06-30): 서비스 배포 음성은 상업 라이선스가 명확한 대체 TTS provider로 간다. Higgs 무참조 synthetic 샘플과 dev-only sample provider는 개발 플래그로만 유지하고, 실존 인물/성우 ref 사용 금지를 유지한다.

방향: 대체 TTS provider 채택 → provider 선정·통합이 후속

결정됨데이터주권·레이턴시

fast-loop 외부 API 허용 여부

소유자 결정(2026-06-30): 로컬 상주 분류기를 기본으로 하되 외부 Haiku/Solar 폴백을 허용한다. 외부 전송 시 데이터주권·로그 보존 정책과 동의 범위를 함께 적용한다.

방향: 외부 폴백 허용 → 폴백 경로·전송 가드 구현이 후속

결정됨운영 감사·장애 대응

운영 보존기간·에스컬레이션 정책

소유자 결정(2026-06-30): 헬스 raw 이벤트 90일, 일별 rollup 365일 보존. 우선순위 escalation은 운영자 수동 승인, SLA는 미표기(현 값은 관측 샘플 이력으로 유지), 담당 그룹 자동 배정은 도입하지 않는다.

방향: raw 90일/rollup 365일 · 수동 escalation · SLA 미표기 → retention 기본값 적용이 후속

결정됨동의서·데이터셋

재귀학습 fine-tuning 범위

소유자 결정(2026-06-30): few-shot 자동갱신에 더해 fine-tuning까지 범위에 포함한다. 단 실제 fine-tuning 데이터셋은 동의 범위·축어록 수급 결정이 닫힌 뒤에 구성한다.

방향: fine-tuning 포함 → 동의/데이터셋 게이트 충족 후 파이프라인 구축

결정됨안전 정책

자유연습 기본값

결정: 기본 ON + 피드백 권장 배지 + 교수자 토글. 미동의·미성년 개인정보 gate만 차단한다.

근거: MASTERPLAN_REVISIONS F-10 승인

결정됨평가 타당도·다양성

추가 축어록 수급 규모/일정

소유자 결정(2026-06-30): 추가 사례 20건을 1개월 내 수급한다. n=1 시드 한계를 해소하고 페르소나 다양성·Phase 3 측정력을 확보하기 위한 목표다.

영향: 평가 타당도, 페르소나 다양성, Phase 3 측정력. 실제 수급·동의·정제는 임상/거버넌스 게이트와 연계.

🤝 외부·임상팀 조율 0
조율위기게이트·평가·이론

임상팀 콘텐츠 (구훈정·어유경)

필요: C1 루브릭 항목·척도·가중치·anchor 예시·컷오프·검수 기준의 임상팀 확정본, CBT 이론별 단계 프롬프트 체인, 위기 스크립트·생명유지서약 문안, H2 평가 골든셋. 코드는 C1 schema/loader/validation scaffold까지만 준비됨.

왜 당신인가: doc3 R&R상 콘텐츠는 임상팀 산출물. 코드는 '편집 가능 구조'만 선제 구축 → 조율·수급 필요.

거버넌스계약·일정

스택 정합성·저작권 등재 (L1)

방향은 Node.js 교체 가능성 우선으로 정했다. 남은 일은 FastAPI 유지 사유, 계약 우선 전환 계획, 저작권 등재 문서 반영을 거버넌스 증거로 남기는 것이다.

권고: docs/decisions/backend-node-transition.md 기준으로 신청서/등재 문서의 스택 설명을 정합화한다.

진행 현황

아래 "상태 보드"의 작업 카드에서 자동 집계. 완료/진행/계획 비율과 트랙별 완료도. (Phase 진행률은 단계별 증거 기반 추정치 — 정밀 수치 아님)

상태 분포

추적 작업 0건 기준

완료 0 진행 0 계획 카드 0

트랙별 완료도

카테고리별 완료 카드 / 전체 카드

관계도 — 무엇이 무엇을 막는가

왼쪽 내 차례(clay) 노드를 풀면 가운데 게이트/작업이 열리고, 오른쪽 납품·마일스톤으로 이어진다. 화살표를 따라가면 "내가 지금 풀어야 임계경로가 뚫리는 것"이 보인다.

내 차례 (OWNER) 게이트 · 작업 납품 · 마일스톤 OAuth /turn 증명 백엔드 언어 결정 임상팀 콘텐츠 평가설계 (H1) 한신대 거버넌스 Phase 0 마감 Phase 1 MVP 위기게이트 1091차 구현 · 검증 교수자 성장 UI1차 구현 · 검증 평가 KPI (H1) Phase 1 GO 납품 필수조건법적 생존선 Phase 3 파일럿
내 차례 — 윤찬이 풀어야 열림 납품 필수 — 미구현 임계작업 게이트·마일스톤 ··· 점선 = 약한/아키텍처 의존

디자인 시안 지침 — 기능 추출 기반 이미지 생성

2026-06-27 기준 주요 페이지 기능을 추출해 PC·태블릿·모바일 반응형 시안 7장과 온화한 이미지 요소 보드 2장을 만들었다. 결과물은 전부 PNG 래스터 이미지이며, 구현 확정안이 아니라 레이아웃·톤·이미지 요소 판단 기준이다.

프롬프트 문서

적용 원칙: 생성 이미지 안의 문구는 참고하지 말고 실제 카피는 코드/기획서 기준으로 다시 쓴다. 세션 아바타는 기존 서연/P1 아바타 핸드오프와 `rasterArtSet` 지침을 우선한다. 교수·관리자 내비게이션은 현재 `AppShell`·`Sidebar` 구조를 유지하고, 배경 일러스트·패턴은 빈 상태·히어로·세션 주변 질감에 소량만 쓴다.

1차 적용(2026-06-27): 온화한 이미지 요소 보드는 구현 참고용으로만 두고, 앱 배경에는 전용 래스터 텍스처 `apps/web/public/design-elements/clinical-paper-ambient.png`를 연결했다. 보드·타일셋·누끼 시트 형태 이미지는 직접 UI 배경으로 쓰지 않는다.

2차 적용(2026-06-27): 생성된 관리자/설정/리뷰 시안 기준으로 admin 역할의 상단바·사이드바를 어두운 운영 콘솔 크롬으로 전환했고, 관리자 사용자 관리 행을 조밀한 운영 테이블 밀도로 낮췄다. 회기 리뷰는 헤더·인사이트 카드·피드백 스테이지에 따뜻한 래스터 질감과 낮은 채도 틴트를 적용했다.

3차 적용(2026-06-27): 상담 세션 시작/진행 화면을 시안 기준의 어두운 상담 스테이지로 재정렬했다. 시작 전 화면은 내담자 무대·브리핑·진행 초점 3영역으로 유지하고, 진행 중 화면은 어두운 캔버스 위에 중앙 스테이지와 자막/컨트롤을 배치한다.

4차 적용(2026-06-27): 교수 콘솔은 시안의 triage 콘솔 구조에 맞춰 KPI를 전폭 6칸으로 재배치하고, 종료 회기 검토 큐와 최근 회기 기록을 첫 화면 2열 핵심 영역으로 올렸다. 학생별 장기 추이와 전체 회기 타임라인은 별도 학생 분석 메뉴로 분리하고, 교수자 사이드바는 좁은 teal rail로 전환했다.

5차 적용(2026-06-27): 회기 리뷰는 시안의 좌·중·우 작업대 구조에 맞춰 요약/액션 레일, 감정·흐름 중앙 영역, 우측 인사이트 패널로 재배치했다. 모바일에서는 트랜스크립트를 워크시트보다 먼저 노출하고, 사례개념화 워크시트는 제한 높이의 보조 패널로 낮춰 긴 문서처럼 보이지 않게 했다.

6차 적용(2026-06-27): 설정 화면은 글로벌 셸을 숨기고 설정 전용 좌측 레일을 주 내비로 승격했다. 계정, AI 운영, 테마, 음성, 알림 카드를 시안의 차분한 설정 워크스페이스 흐름에 맞춰 단일 컬럼으로 재배치했고, 모바일은 상단 고정 섹션 탭으로 모든 항목이 잘리지 않게 정리했다.

7차 적용(2026-06-27): 로그인 화면은 별도 생성한 래스터 상담실 배경(login-counseling-room.png)을 왼쪽 브랜드 패널에 적용해 시안의 실제 상담실 분위기를 반영했다. 학습자 홈은 모바일 grid 행 계산 때문에 생기던 리스트-상세 간 큰 공백을 제거하고, 회기 준비 보드가 한 흐름으로 이어지게 정리했다.

8차 적용(2026-06-27): 다크 테마가 라이트 primitive 토큰과 역할 accent에 덮여 흰 섹션이 섞이던 문제를 수정했다. 앱 부트 시 테마를 먼저 적용하고, 다크 모드에서 paper/surface/hair/ink primitive까지 스왑한다. 상담 세션 진행 화면은 테마 토글과 무관하게 어두운 콘솔 패널로 고정했고, 드래그형 종료 제스처는 명시적 회기 종료 버튼 + 확인 다이얼로그로 교체했다. 회기 리뷰도 동일한 다크 카드/축어록/워크시트 재질로 보정했다.

9차 적용(2026-06-27): 관리자 콘솔 IA를 /admin 운영 홈과 /admin/users 사용자 관리, /admin/access 접근 권한, /admin/tickets 운영 티켓 메뉴로 분리했다. 운영 홈은 실제 API가 있는 교육 진행·온라인 사용자·헬스·AI 비용·서비스 리소스를 우선 표시하고, 티켓/업타임 이력은 app.support_ticket·app.admin_health_event 기반 저장 모델로 연결했다. 세분 그룹 권한 enforcement는 아직 정책 화면이며 가짜 권한 효과를 표시하지 않는다.

17차 적용(2026-06-28): 운영 홈의 티켓 요약은 resolved/closed 이력을 진행 중 위험으로 세지 않는다. high_priority_count와 미니 티켓 목록은 미해결 티켓 기준이며, E2E 운영 티켓 테스트는 제목·본문에 테스트임을 명시하고 실패 시에도 cleanup patch로 해결 처리한다.

19차 적용(2026-06-28): /admin/tickets는 현재 범위를 미해결 큐 목록과 상태 변경으로 좁혔다. 정적 분류/인사이트 탭은 제거하고, 카드에 카테고리·신고자·경로·접수/갱신 시각을 표시한다. 해결된 항목은 최근 해결 이력으로 내려간다. 필터·카테고리 큐는 이후 26차에서 처리했고, Claude Recipe headless 자동 처리·이슈 등록은 백로그 운영 티켓 자동 분류·처리 2차로 남겼다.

26차 적용(2026-06-28): /admin/tickets에 검색, 상태·카테고리·우선순위·담당그룹·정체 필터와 카테고리 quick queue를 추가했다. 서버는 동일 query를 DB에서 처리하고, 티켓 접수 및 관리자 수동 처리 변경은 audit.audit_log에 metadata-only로 남긴다. Claude Recipe 자동 수정 후보, 이슈/PR/작업 스레드 생성은 운영자 승인 전까지 계속 후속으로 둔다.

28차 적용(2026-06-28): 학습자 Settings에 지원 요청 섹션을 추가했다. GET /users/support-tickets는 현재 사용자 reporter_id의 티켓만 최신순으로 반환하고, 본문·신고자 이메일·신고자 이름은 응답에서 제외한다. Settings는 상태·카테고리·우선순위·담당 그룹·해결 메모만 읽기 전용으로 보여주며, 조회 실패는 설정 전체 로딩을 막지 않는다.

29차 적용(2026-06-28): /admin/health와 synthetic sampler가 같은 record_admin_health_sample() 경로를 사용한다. scripts/record-admin-health-sample.py는 브라우저 admin 세션 없이 app.admin_health_event에 서비스별 샘플을 append하고, scripts/install-health-sampler-task.ps1는 Windows Scheduled Task로 주기 실행을 설치한다. 최신 로컬 one-shot은 recorded_count=5, status=ok, engine_mode=claude_cli다.

30차 적용(2026-06-28): 헬스 샘플 이력은 raw app.admin_health_event와 daily app.admin_health_daily_rollup으로 분리했다. scripts/maintain-admin-health-events.py--rollup-days/--retention-days 명시 인자를 요구하고 기본 dry-run으로 동작한다. apply 모드는 rollup upsert 후 retention window 밖 raw row만 삭제하며, non-dev apply는 추가 확인 플래그 없이는 막는다. /admin/uptime 요약은 raw+rollup을 중복 없이 합산하고 상세 이벤트 목록은 최근 raw 샘플만 보여준다.

31차 적용(2026-06-28): 운영 티켓 중복 처리는 자동 병합이 아니라 수동 연결 1차로 닫았다. 사용자 티켓 생성은 결정론적 fingerprint를 저장하고, /admin/tickets는 같은 fingerprint 후보 수·oldest parent 후보·하위 티켓 수를 반환한다. 관리자는 기존 PATCH /admin/tickets/{id}parent_ticket_id를 지정/해제하며, self-parent와 cycle은 서버가 거부한다. Settings의 사용자별 티켓 조회에는 fingerprint/parent를 노출하지 않는다.

27차 적용(2026-06-28): 백엔드 언어 방향은 Node.js 우선 교체 가능성으로 정리하되 FastAPI 전면 재작성은 보류했다. 첫 경계로 app/contracts/engine_gateway.py를 추가해 /v1/generate, /v1/stream, SSE token/done/error 계약을 FastAPI client와 현재 Python gateway가 함께 쓰게 했다. 결정 기록은 docs/decisions/backend-node-transition.md에 둔다.

32차 적용(2026-06-28): Engine gateway SSE parsing을 app/contracts/engine_gateway.pytoken/done/error wire contract로 이동했다. gateway token payload, done telemetry, error detail 검증은 공유 계약 decoder가 맡고, gateway error는 저장 턴을 만들지 않는 기존 경계를 유지한다. 34차에서 이 raw line 해석 소유권은 EngineClient.stream_packets() 경계로 올려 Node.js gateway 교체를 위한 계약 증거를 강화했다.

33차 적용(2026-06-28): H1 pre/post evidence 원장을 학습자 SessionReview 화면에 연결했다. 학습자는 자기효능감·기술숙련도·수련만족도 3척도의 사전/사후 1~5 aggregate score를 조회·저장할 수 있고, 화면 문구는 효과 판정·통계 검정이 평가설계 확정 이후 별도 산출임을 명시한다. 구현 중 React 이벤트 값을 상태 updater 안에서 늦게 참조해 앱이 검은 배경만 남는 크래시도 확인해 수정했다.

34차 적용(2026-06-28): Node.js gateway 교체 가능성을 위해 엔진 스트림 wire parsing 소유권을 EngineClient.stream_packets()로 올렸다. 앱 orchestrator는 raw SSE line이 아니라 EngineGatewaySsePacket만 처리하고, Python gateway의 /v1/generateGenerateResponse.model_dump()로 반환해 hand-mirrored dict drift를 줄였다. focused gateway/orchestrator/session 회귀는 46 passed.

35차 적용(2026-06-28): X2 비용 통제 후속으로 fast/deep evaluator structured 결과에 인메모리 semantic cache 1차를 추가했다. 캐시 키는 canonical evaluator request의 SHA-256이며 원문 prompt·completion은 저장하지 않는다. 성공 파싱 결과만 TTL/entry 제한 안에서 재사용하고, engine error·parse error는 캐시하지 않는다. focused evaluator/runtime/admin 회귀는 40 passed.

36차 적용(2026-06-28): H1 pre/post 원장에서 Phase 3 evidence root용 02-measures/prepost_measures.csv02-measures/kpi_report.json을 생성하는 1차 계산기를 추가했다. participant id는 P3-###로 가명화하고, 3척도 paired normalized mean pre/post/delta, complete/missing pair를 report에 채운다. 공식 효과성 판정·통계검정·실험/통제군 배정은 아직 평가설계 게이트다. focused H1/backend 회귀는 45 passed.

37차 적용(2026-06-28): H4 한국어 NAME/ORG 마스킹에 합성 fixture 기반 평가 harness를 추가했다. 초기 NAME/ORG baseline은 scripts/evaluate-pii-masking.py로 entity recall·forbidden substring removal·unexpected entity violation을 출력했고, fixture가 잡은 소속/안내 NAME 오탐은 stopword로 보정했다. 이 baseline은 62차에서 11-case schema-backed dry-run으로 확장했다.

38차 적용(2026-06-28): M2 다회기 케이스 아크 2차로 세션 종료 시 마스킹 축어록 기반 fallback session_summary.digestcase_profile.case_digest, rapport_trajectory, alliance_level을 갱신한다. 다음 회기 seed recall은 case_digest·직전 요약·client-visible pinned_fact를 함께 조립한다. 당시 후속이던 pinned_fact 자동 쓰기는 43차에서 보수적 identity/agreement 범위로 연결했다. focused M2 회귀는 43 passed.

39차 적용(2026-06-28): X2 evaluator semantic cache 운영 관측을 /admin/usage와 관리자 AI 비용 카드에 노출했다. 응답은 cache key나 prompt/completion 없이 enabled, entries, hits, misses, stores, evictions, requests, hit_rate만 반환한다. 당시 X2/backend focused 40 passed였고, 최신 X2 리포트 라운드는 42 backend passed + CLI sample OK다. API typecheck/build, admin E2E 1 passed.

40차 적용(2026-06-28): H3 페르소나 draft 생성 프롬프트에 persona-draft-rag@2026-06-28.1 bundle pinning을 추가했다. POST /personas/drafts/generate는 engine metadata와 draft source_provenance에 prompt bundle id/version/hash를 남겨 source/chunk 증거와 생성 프롬프트 버전을 함께 추적한다. focused persona review 회귀는 31 passed.

41차 적용(2026-06-28): X2 장기 비용 추이 1차로 /admin/usage에 일별 daily_cost bucket(day, turns, tokens_in/out, cost_usd)을 추가하고, 관리자 AI 비용 카드에 최근 일별 비용/턴 추이를 표시했다. DB와 dev runtime fallback 모두 같은 shape를 반환한다. 자동 차단·한도 enforcement는 운영 정책 결정이 필요해 후속으로 남긴다. backend 40 passed, API typecheck/build, admin E2E 1 passed.

42차 적용(2026-06-28): M1 비언어 이벤트 2차로 voice learner turn에 provider_events JSONB 보존 슬롯을 추가했다. WebSocket control/STT provider 이벤트는 allowlist·개수·문자열 길이 제한을 거쳐 app.turns.provider_events에 저장되고, raw transcript/text payload는 보존하지 않는다. 당시 기준으로 review/public API에는 아직 노출하지 않고 기존 nonverbal 칩은 audio/silence/pace/barge-in 파생만 유지했다. focused voice/session 회귀는 44 passed.

43차 적용(2026-06-28): M2 다회기 메모리 3차로 세션 종료 시 마스킹된 client-visible 발화에서 보수적 pinned_fact 후보를 자동 실적재한다. 1차 범위는 [NAME]/[ORG] identity와 명시적 상담 약속만이며, 관계갈등·위기·임상 추론은 pinning하지 않는다. learner-owned case_profile 기준 RLS insert/update 정책을 추가했고, 다음 회기 recall은 기존 client-visible pinned_fact 읽기 경로를 그대로 사용한다. focused M2 회귀는 60 passed.

44차 적용(2026-06-28): M2 다회기 메모리 4차로 pinned_fact 삽입 또는 값 변경 시 pinned_fact_history에 append-only 이력을 남긴다. 같은 값 재확인은 history를 늘리지 않고, locked fact는 계속 건드리지 않는다. 이유값은 최초 기록 progression, 값 변경 clarification로 제한했으며, 자동 모순 판정은 아직 후속이다. focused M2 회귀는 62 passed.

45차 적용(2026-06-28): M1 비언어 이벤트 3차로 저장되는 provider_events에 내부 taxonomy event_type/category를 붙인다. raw provider type/kind/label은 보존하되 sigh/cry/laugh/voice_activity/noise 등은 canonical key로 정규화한다. raw transcript/text payload는 계속 drop했고, 당시 기준으로 review/public API 노출은 하지 않았다. focused voice/session 회귀는 45 passed.

46차 적용(2026-06-28): C3 이론모드 2차로 세션 시작 전 화면에 humanistic/cbt/integrative 명시 선택 segmented control을 추가했다. 기본값은 기존처럼 페르소나 theory_target에서 잡고, 사용자가 바꾼 값은 POST /sessionstheory_mode로 전송된다. CBT 체인·임상 문안·이론부합 루브릭은 만들지 않았다. 검증: web typecheck/build, C3 backend focused 31 passed, session-layout desktop/mobile 8 passed, session-mvp 1 passed, layout visual gate 9 passed.

47차 적용(2026-06-28): H3 seed/version materializer runner 1차로 scripts/materialize-persona-seeds.py를 추가했다. 기본은 DB를 건드리지 않는 dry-run manifest 보고이고, --json은 P1~P7 seed_version/persona_id/source_provenance를 기계 판독 형태로 내보내며, --apply일 때만 DB pool을 초기화한 뒤 기존 idempotent materialize_seed_personas()를 호출한다. 당시 후속이던 source pack content_hash/document version 증분 sync runner는 48차에서, 원문 격리 정책 1차는 55차에서 처리했다. 검증: runner py_compile, py -3.11 scripts\materialize-persona-seeds.py --json dry-run manifest_count=7, focused persona review 33 passed.

48차 적용(2026-06-28): H3/H2 repo-managed source pack version sync runner 1차로 app.services.source_pack_syncscripts/sync-persona-sources.py를 추가했다. 기존 POST /kb/live-coach/source-packs/sync도 같은 service를 사용하며, active kb.documentcontent_hash가 같으면 skip하고 다르면 previous_version + 1로 색인해 UNIQUE(source_id, doc_uri, version) 충돌을 피한다. dry-run은 DB 상태를 읽되 write하지 않고, --apply에서만 kb.source upsert와 kb.document/kb.chunk 색인을 수행한다. 당시 남은 원문 격리 정책 1차는 55차에서 처리했고, 임상팀 최종 검수 evidence는 여전히 후속이다. 검증: source sync py_compile, py -3.11 scripts\sync-persona-sources.py --help, test_live_coach_sources.py 4 passed.

49차 적용(2026-06-28): M1 비언어 이벤트 4차로 인증된 회기 리뷰의 nonverbal 칩에 정규화된 provider taxonomy 일부를 제한 노출한다. sigh/cry/laugh/breath, prosody, background noise 같은 event_type은 한글 label/detail로 파생하고, provider/source/raw type/text/transcript는 응답에 싣지 않는다. 공개 공유 카드도 축어록과 provider raw를 계속 제외한다. 검증: M1 backend focused 45 passed, npm run check:api-types, npm run typecheck, npm run build, session-review.spec.ts 3 passed.

50차 적용(2026-06-28): M2 pinned fact 모순 처리 1차로 명시적 상담 약속 철회/부정만 agreement:counseling 기존 fact의 status='contradicted'로 격리한다. 새 contradicted fact를 임의 생성하지 않고, non-locked 기존 fact가 있을 때만 업데이트하며 history reason은 contradiction으로 남긴다. 관계갈등·위기·임상 추론과 값 다름 일반 케이스는 계속 자동 모순 처리하지 않는다. 당시 검증: test_session_memory.py 11 passed, M2 focused 64 passed.

51차 적용(2026-06-28): L1 Node.js 전환 계약 증거 2차로 apps/api/engine_gateway/golden/engine_gateway_contract.v1.json golden fixture와 engine_gateway_schema.v1.json schema artifact를 추가했다. fixture는 /v1/generate request/response, /v1/stream token/done/error frame과 decoded packet, provider [DONE] compatibility line을 고정한다. schema는 Node.js gateway가 Python import 없이 검증할 request/response/event shape를 고정한다. Python 계약 테스트가 schema drift와 fixture validation을 같이 보므로, 미래 Node.js gateway는 Python JSON whitespace가 아니라 같은 artifact의 의미론적 packet 동등성으로 conformance를 증명하면 된다. 당시 검증: gateway contract 18 passed, L1 focused 49 passed.

52차 적용(2026-06-28): L1 Node.js 전환 계약 증거 3차로 scripts/check-engine-gateway-contract.mjs conformance runner를 추가했다. 이 runner는 Python import 없이 golden fixture와 schema artifact를 읽고, generate request/response shape, stream token/done/error frame decoding, provider [DONE] compatibility line 무시를 검증한다. engine_gateway/test_gateway_model.py도 Node runner를 subprocess로 호출해 Python 계약 테스트와 Node 계약 검증이 같은 artifact를 공유하도록 했다. 검증: node scripts/check-engine-gateway-contract.mjs --json, gateway contract 19 passed, L1 focused 50 passed.

53차 적용(2026-06-28): M2 episodic embedding writer 1차로 세션 종료 저장 성공 뒤 _write_episodic_embeddings() background task를 예약한다. writer는 speaker='client', turn_id 있음, client visible, text_masked nonempty 조건만 통과시키고 원문 text, 상담자 발화, evaluator-only turn, 평가 payload는 색인하지 않는다. app.turn_embedding 쓰기는 BGE-M3 dense/sparse를 ON CONFLICT (turn_id) DO NOTHING으로 idempotent 저장하며, DB/pgvector/embedder 미구성은 종료 저장을 실패시키지 않는다. 검증: test_session_memory.py 14 passed, M2 focused 67 passed.

54차 적용(2026-06-28): X2 모델별 비용 리포트 1차로 app.services.usage_reportscripts/report-ai-usage.py를 추가했다. 기존 /admin/usage JSON을 입력으로 provider/model별 cost share, token share, cost/turn, cost/1k tokens, metered coverage, budget/cache warning을 산출하되 자동 차단이나 한도 enforcement 정책은 만들지 않는다. 검증: usage report 2 tests, X2 focused 42 passed, CLI sample report schema vignette.ai_usage_model_cost_report.v1 생성.

55차 적용(2026-06-28): H3 원문 격리본 정책 1차로 kb.raw_source_artifact hash-only 레코드를 추가하고, POST /personas/sources가 raw 원문 hash와 sanitized RAG source를 분리해 기록하게 했다. rag.index_document()sensitivity=3 또는 raw source marker 청크를 DB 접근 전에 IndexPolicyViolation으로 차단하므로 raw 원문은 kb.chunk/embedding/FTS로 들어가지 않는다. 암호화 blob/vault 저장은 후속. 검증: raw isolation py_compile, H3 focused 38 passed.

56차 적용(2026-06-28): H2 회기 리뷰 workbench 1차로 학습자 SessionReview 데스크톱 레이아웃을 축어록 타임라인과 요약·감정·흐름·루브릭·강점·개선점·pre/post·워크시트·피드백 작업영역으로 재배치했다. 교수자 검토 화면과 모바일/태블릿 반응형은 기존 규칙을 유지한다. 검증: web typecheck, session-review.spec.ts 3 passed, layout-visual-gate.spec.ts 9 passed, 1440 desktop screenshot 직접 확인.

57차 적용(2026-06-28): L1 Node.js 전환 준비 4차로 브라우저-facing 세션 read-model을 app/session_read_model.py로 분리했다. routes/sessions.py는 route/auth/RLS DB read/persistence를 유지하고, 세션 목록·대시보드·상세·리뷰·공유 payload DTO와 deterministic builder는 read-model 모듈이 소유한다. OpenAPI field shape와 hidden-turn/evaluator-only filtering, saved worksheet 우선순위, teacher review read-only 경계는 유지했다. 검증: session read-model py_compile, focused backend 48 passed, npm run check:api-types, npm run typecheck.

58차 적용(2026-06-28): 학습 세션 텍스트 턴은 학습자 발화 버블을 전송 즉시 표시하고, 내담자 응답 전에는 임시 대기 버블만 보여준다. 채팅 입력은 응답 생성 대기 중에만 막고, 음성 TTS 재생 중에는 자막이 먼저 남은 상태에서 텍스트 입력을 유지하며 음성 건너뛰기 버튼으로 재생을 중단할 수 있다. 검증: web typecheck, session-mvp.spec.ts 1 passed, voice-success.spec.ts synthetic UI 1 passed.

59차 적용(2026-06-28): L1 Node.js 전환 준비 5차로 브라우저-facing 페르소나 DTO와 deterministic mapper를 app/persona_read_model.py로 분리했다. routes/personas.py는 route/auth/teacher-admin gate/DB repository/RAG source 등록/LLM draft generation/error mapping을 유지한다. PersonaSummary, PersonaReviewSummary, PersonaDraftPayload, PersonaDraftDetail, source/evidence 응답 schema 이름과 source/degraded fail-closed catalog 경계는 유지했다. 검증: persona read-model py_compile, test_persona_review.py 33 passed, persona/session-start 55 passed, npm run check:api-types, npm run typecheck.

60차 적용(2026-06-28): H3 항목형 저작 UI 9차로 PersonaStudio의 자동사고, 회기 시나리오, 말투 목록, 역린·금기 목록을 줄바꿈 textarea에서 행 추가/삭제 UI로 바꿨다. 저장 payload는 기존 배열 schema를 유지하고, 빈 항목은 저장 직전에 제거한다. 검증: npm run typecheck, npm run build, teacher.spec.ts 7 passed, layout-visual-gate.spec.ts 9 passed, session-layout.spec.ts desktop/mobile 8 passed.

61차 적용(2026-06-28): H3 프롬프트 미리보기 10차로 PersonaStudio 프롬프트 탭의 raw JSON textarea를 제거하고, 같은 draft 데이터를 L1 카드·인적 범주·임상 배경·말투·수치 파라미터·역린·회기 시나리오·추가 계약 섹션으로 검토하게 했다. 런타임 prompt bundle, API 계약, 임상 문안은 바꾸지 않았다. 검증: npm run typecheck, npm run build, npm run check:api-types, teacher.spec.ts 8 passed, 프롬프트 탭 포함 layout-visual-gate.spec.ts 9 passed.

62차 적용(2026-06-28): H4 마스킹 평가 준비 3차로 합성 fixture를 5건에서 11건으로 확장하고, NAME/ORG 외 PHONE/EMAIL/RRN/NUMID/DATE/MONEY/ADDR/negative-control까지 source/category/severity metadata로 평가한다. pii-masking-eval-input.schema.jsonpii-masking-eval-report.schema.json을 추가했고, 기본 리포트는 masked_text 원문 증거를 빼는 summary-only technical_dry_run이다. 실제 운영 말뭉치 기반 평가는 아직 외부 데이터·reviewer label·법무/guardian evidence gate다. 콘솔의 /favicon.ico 404는 SVG favicon link 추가로 해소했다. 검증: H4 focused 42 passed, CLI fixture 11/11 pass, py_compile.

63차 적용(2026-06-28): C1 루브릭 외부화 3차로 data/rubrics/case-worksheet-rubric.json scaffold와 Draft 2020-12 schema, app.services.case_worksheet_rubric, scripts/check-case-worksheet-rubric.py를 추가했다. scaffold는 서버 생성 워크시트 5개 section/28개 item key와 맞는지만 검증하고 status=scaffold_only, scoring_enabled=false를 유지한다. 임상팀 확정 criteria/anchor/가중치/컷오프 없이는 AI 채점이나 점수 노출을 켜지 않는다. 검증: rubric focused 4 passed, CLI PASS with sha256 provenance, py_compile.

64차 적용(2026-06-28): refactor-governance 1차로 C1 생성 워크시트 section/item spec을 CASE_WORKSHEET_SECTION_SPECScase_worksheet_template_item_keys()로 올려 루브릭 validator/CLI/test가 더미 ReviewTurn 없이 같은 템플릿 키를 참조하게 했다. API/DB/frontend 응답 shape와 저장본 우선순위는 바꾸지 않았다. 검증: C1 focused 34 passed, rubric CLI PASS, py_compile, residual grep.

65차 적용(2026-06-28): refactor-governance 2차로 프론트 inline CSS template literal을 제거했다. Loginpages/login/LoginBrand.tsx, LoginPanel.tsx, login.css로 분리해 인증 상태 로직과 프레젠테이션을 나눴고, PendingApproval, Onboarding, LearnerHome, Professor, PersonaStudio, ClientAvatar*_CSS 상수는 인접 CSS 파일 import로 옮겼다. 검증: inline <style>{..._CSS}</style> residual 0건, npm run typecheck, npm run build, 로그인 Playwright 렌더 스모크.

66차 적용(2026-06-28): H4 자연 발화형 한국어 이름 마스킹 보강으로 제 이름은 김서연입니다, 보호자 이름은 박민수입니다, 저는 최하늘입니다 패턴과 이름은 중요하지 않다 negative control을 합성 fixture에 추가했다. 이름 stopword와 자기소개형 이름 패턴을 generic 성씨+조사 패턴보다 앞에 두어 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 검증: H4 focused 43 passed, CLI fixture 15/15 pass, py_compile.

67차 적용(2026-06-28): M2 다음 회기 회상 주입 증거를 route-level로 고정했다. REST submit과 SSE stream이 공통 _prepare_turn_context() 경계에서 seed recall, pinned fact, 최근 client-visible 턴, KB cue를 조립하게 했고, 새 테스트가 DB case_digest·직전 session_summary·client-visible pinned_fact가 다음 턴 EngineMessage의 L2/L4 블록에 들어가며 raw 이름은 [NAME]으로 마스킹되는지 검증한다. 검증: M2 focused 68 passed, py_compile.

68차 적용(2026-06-28): refactor-governance P1 delete-only로 routes/sessions.py의 미사용 _load_prev_case_summary()와 죽은 _LEARNER_VISIBLE_AI_ROLE 상수를 제거했다. 동작 변경 없이 다음 M2 작업 표면을 줄인 정리다. 검증: residual grep 0건, M2 focused 68 passed, py_compile.

69차 적용(2026-06-28): refactor-governance P3 계약 축소로 리뷰 도식 ReviewPhaseSegment.key/label, SessionReviewResponse.reachedPhase, 평가 조회 EvaluationSummary.stage, 평가 트리거 응답 stage, 교수 콘솔 TeacherSessionSummary/TeacherGrowthPoint stage를 OpenAPI union으로 좁혔다. 공유 카드 reachedPhase와 live-coach event stage는 저장 payload/legacy 호환 검토가 필요해 별도 후속으로 남겼다. 검증: backend focused 50 passed, npm run check:api-types, npm run typecheck, npm run build.

70차 적용(2026-06-28): H4 마스킹 경계를 refactor-governance 기준으로 좁혀 guardrail.mask_pii() 내부에 선택형 한국어 PII recognizer adapter를 추가했다. adapter는 import-time hard dependency가 아니며 실패 시 예외를 삼키고 기존 regex fallback을 마지막 안전망으로 유지한다. fake adapter 회귀는 regex가 못 잡는 별명/기관 span을 [NAME]/[ORG]로 마스킹하고 같은 문장의 전화번호는 후단 regex가 [PHONE]으로 처리하는지, adapter 실패 시에도 regex fallback이 유지되는지 검증한다. span 중복 처리는 문자별 set 할당 대신 정렬된 last_end 비교로 정리했다. 검증: H4 focused 47 passed, CLI fixture 15/15 pass, py_compile.

71차 적용(2026-06-28): refactor-governance P3 계약 축소 후속으로 공유 카드 PublicSessionShareResponse.reachedPhaseLiveCoachEvent.stage도 OpenAPI enum/null 계약으로 좁혔다. 저장된 legacy JSON/TEXT 값은 stage_label_or_none() validator에서 한글 라벨 또는 기존 code key만 살리고, 빈 문자열·임의 문자열은 null로 낮춰 기존 공유 링크·live-coach 이력이 500으로 깨지지 않게 했다. 세션 UI 코칭 이력은 null stage를 단계 미상으로 표시한다. 검증: share/live-coach focused 43 passed, npm run check:api-types, npm run typecheck, npm run build.

72차 적용(2026-06-28): refactor-governance P3 계약 SSOT 후속으로 stage 라벨과 리뷰 phase key 변환 규칙을 app/stage_contract.py로 분리했다. turn_runtime.py의 legacy code 정규화, session_read_model.pyStageLabel/ReviewPhaseKey, routes/eval.py의 nullable summary stage membership 체크가 같은 모듈을 참조한다. OpenAPI 응답 shape는 유지하고, enum/legacy/unknown 처리만 한 곳으로 모았다. 검증: stage contract focused 42 passed, npm run check:api-types, npm run typecheck, npm run build.

73차 적용(2026-06-28): C1 워크시트 교수자 수동 검수 골격을 추가했다. 기존 app.session_review_statusworksheet_status/worksheet_note/worksheet_reviewed_at을 붙이고, PUT /teacher/sessions/{id}/review-status가 승인·수정요청·반려 결정을 저장한다. /sessions/{id}/reviewteacherReview는 교수자/관리자에게만 이 결정을 노출하고, SessionReview 교수자 카드에는 학습자 저장 워크시트가 있을 때만 수동 검수 버튼을 활성화한다. AI 채점·루브릭 점수·자동 승인·재제출 잠금 정책은 만들지 않았다. 검증: C1 focused 40 passed, API types/typecheck/build, session-review E2E 3 passed.

74차 적용(2026-06-28): refactor-governance P4로 H2 live-coach source pack 캐시 경계를 명시했다. live_coach의 로컬 source pack snapshot은 API 프로세스 생명주기 캐시로 유지하고, 관리자 POST /kb/live-coach/source-packs/syncscripts/sync-persona-sources.pyrefresh=True로 캐시를 비운 뒤 repo 파일을 다시 읽는다. live turn 경로에는 매 턴 파일 IO를 추가하지 않았고, RAG content_hash/version 비교와 evaluator-only payload 정책은 유지했다. 파일 변경 뒤 stale hash가 refresh로 새 hash/version bump에 반영되는 회귀를 추가했다. 검증: source pack focused 9 passed, H2 related 44 passed, sync CLI help, py_compile.

75차 적용(2026-06-28→29): 다크 UI v2 시안을 실제 코드 적용 라운드로 완료했다. P1 라이브 상담 세션은 Session.tsx/session.css 구조까지 조정해 내담자 무대 발화, 좌측 세션 진행, 우측 라이브 신호/안전 점검을 다크 훈련 화면으로 재구성했다. P2 회기 리뷰는 learner filled 상태를 3열 workbench로 바꿔 요약/흐름, 축어록, 평가 rail, 워크시트를 분리했고 empty review는 2열 이하로 유지했다. P3 학습자 홈은 대시보드에서 연습 대상 레일을 제거하고, 진행 회기/리뷰 대기/최근 평가/라포 흐름을 상단 CTA 바로 올렸으며, 오늘 이어갈 회기·AI 코치·다음 연습 추천·최근 피드백을 하나의 lh-work-cluster 작업 묶음으로 재배치했다. 오른쪽 레일은 최근 기록, 리뷰 대기, 반복 대상만 담당한다. 새 증거 캡처는 docs/design-verification/full-pages/02-learner-home-{desktop,tablet,mobile}.png, 03-session-active-{desktop,tablet,mobile}.png, 04-session-review-{desktop,tablet,mobile}.png, 04-session-review-empty-{desktop,tablet,mobile}.png다. 검증: npm run typecheck, npm run build, layout-visual-gate.spec.ts single-run 9 passed.

76차 적용(2026-06-28): refactor-governance P2로 M2 digest 계약 경계를 고정했다. SessionDigestInput/SessionDigestResult/SessionSummaryWrite를 추가해 LLM worker 후보가 raw text, evaluator-only turn, 평가 payload, CCD, deterministic carry를 우회하지 못하게 했다. /sessions/{id}/enddigest_pending은 여전히 CompressionJob 생성 여부를 알리는 비동기 압축 필요 신호일 뿐이며, live provider 압축·운영 품질 검증 완료를 뜻하지 않는다. 검증: M2 focused 74 passed, npm run check:api-types, npm run typecheck, py_compile.

77차 적용(2026-06-28): H1 Phase 3 KPI report 계약을 phase3_kpi_contract.py로 모아 exporter/checker/test의 metric 이름·필수키·status 중복 선언을 줄였다. kpi_report.json metric은 computed_prepostdesign_pending status를 구분하므로, 3척도 pre/post로 계산 가능한 파일럿 evidence와 평가설계 전이라 계산하지 않는 KPI를 기계적으로 분리한다. 공식 효과성 판정·통계해석·20명 파일럿 evidence는 계속 외부/파일럿 gate다. 검증: H1 KPI focused 10 passed, H1 broad 46 passed, exporter/checker help, py_compile.

78차 적용(2026-06-29): refactor-governance P3로 L1 Node 전환 준비의 gateway-default 모델 sentinel을 app/contracts/engine_gateway.py 계약 상수와 normalize_engine_gateway_model()로 올렸다. FastAPI EngineClient, 현재 Python gateway, orchestrator stream audit/done fallback, 관리자 기본 engine config가 같은 sentinel을 참조하고, engine_gateway_schema.v1.json과 Node conformance runner도 기본 라우팅 sentinel을 검증한다. gateway-default는 실제 provider 모델명이 아니라 explicit --model을 보내지 않는 gateway 기본 라우팅 신호다. 검증: py_compile, Node conformance OK, 당시 gateway contract focused 20 passed, L1 focused 55 passed, admin/runtime/evaluator focused 40 passed.

79차 적용(2026-06-29): refactor-governance P3/P8로 GenerateResponse 구조화 JSON fallback을 structured_payload_from_response() 계약 helper로 모았다. evaluator, live-coach, persona draft route가 structured field 우선, fenced JSON, embedded JSON object fallback을 같은 경계에서 처리하므로 Node gateway 교체 시 보존해야 할 legacy response 해석 규칙이 한 곳에 남는다. 학습자 SessionReview 입력은 name/autocomplete/aria-label과 공유 focus token(--border-focus, --focus-ring)으로 정리했고, 발화 이동 버튼에도 목적 발화 aria-label을 추가했다. 검증: py_compile, Node conformance OK, gateway contract 23 passed, backend focused 89 passed, npm run check:api-types, npm run typecheck, npm run build, session-review E2E 3 passed, layout visual gate 9 passed.

80차 적용(2026-06-29): refactor-governance P3로 H3/L1 페르소나 draft generation 계약을 app/persona_generation_contract.py로 분리했다. 이 모듈이 draft structured schema, prompt bundle id/version/hash, GenerateResponse payload extraction, generated draft coerce/default fallback을 소유한다. routes/personas.py는 teacher/admin auth, source 등록·조회, RAG evidence, engine 호출, provenance 조립, HTTP error mapping을 계속 소유한다. 검증: py_compile, persona contract+review 39 passed, backend focused 95 passed, Node conformance OK, npm run check:api-types, npm run typecheck.

81차 적용(2026-06-29): refactor-governance P2/P3로 engine structured payload 계약과 session evaluation write packet 경계를 더 줄였다. evaluator/live-coach의 로컬 _structured_payload() alias를 제거해 structured_payload_from_response()를 직접 호출하게 했고, SessionEvaluationWriteapp.session_evaluation 저장 packet(status/source/scope/stage/payload/error)을 소유한다. routes/sessions.pyroutes/eval.py는 named packet을 만들고, SQL·fallback cache·응답 shape는 유지한다. 검증: py_compile과 당시 focused suite 통과, 이후 최신 L1 focused 120 passed와 Node conformance OK로 재검증.

82차 적용(2026-06-29): refactor-governance P8로 Session.tsx의 음성/세션 UI 파생 상태 계산을 sessionVoiceStatusView() helper로 분리했다. 마이크 busy/label, 세션 상태 label, transcript live label, 텍스트 입력 차단, 응답/음성 상태 chip 계산을 named view-model 경계에서 유지한다. API/DB 계약, 세션 시작·전송·종료, voice WebSocket/TTS lifecycle, live-coach payload, DOM/CSS는 변경하지 않았다. 검증: npm run typecheck, npm run build, session-mvp 1 passed, voice-success 2 passed, session-layout desktop/mobile 8 passed.

83차 적용(2026-06-29): refactor-governance P3로 학습자/교수자 성장 지표가 턴 평가 기법 라벨을 읽는 경계를 session_metrics.turn_technique_label()로 명시했다. DB 정규화 후 재수화되는 TechniqueTag.label_ko를 우선 사용하고, 기존 label/name/id/code legacy fallback은 유지한다. alternative_utterances는 future adapter 추출 전에 문자열 배열과 text/suggestion dict 배열을 모두 row로 정규화하는 호환 테스트로 고정했다. 검증: py_compile, evaluation/learner/teacher focused 59 passed.

84차 적용(2026-06-29): refactor-governance P2로 Session.tsx의 음성 guard/view-model 경계를 한 번 더 줄였다. isVoiceStatusBusy()가 마이크 busy 판정을 소유하고, sessionVoiceStatusView()가 마이크 disabled와 aria label까지 반환해 JSX와 toggleMic()의 중복 조건을 제거했다. recording은 계속 busy가 아니므로 두 번째 마이크 클릭으로 발화 전송이 가능하다. API/DB 계약, WebSocket/TTS lifecycle, STT/TTS provider 동작, live-coach payload, DOM/CSS는 변경하지 않았다. 검증: npm run typecheck, npm run build, session-mvp 1 passed, voice-success 2 passed, session-layout desktop/mobile 8 passed.

85차 적용(2026-06-29): refactor-governance P3로 Node 전환 계약 검증의 gateway-default sentinel 중복을 줄였다. scripts/check-engine-gateway-contract.mjs는 더 이상 sentinel 문자열을 자체 상수로 들고 있지 않고, engine_gateway_schema.v1.jsonx-engine-gateway-default-model-sentinel extension을 읽어 schema artifact를 SSOT로 검증한다. 공개 런타임 SSOT도 재확인해 custom domain asset을 assets/index-DgN8JVBw.js/assets/index--s3xqgO5.css로 갱신했고, OAuth start는 동작하지만 인증된 /turn proof는 여전히 owner storageState gate로 남겼다. vnet.18ka.net/api-vnet.18ka.net DNS A record는 0건이라 DONE 처리하지 않는다.

86차 적용(2026-06-29): refactor-governance P2/P3로 Session mic capture를 AudioWorklet-first 구조로 전환하고 기존 MediaRecorder를 fallback으로 남겼다. Worklet은 mono PCM16 chunk를 /voice/ws binary frame으로 보내고, control packet은 format=pcm, sample_rate, channels, sample_width를 명시한다. 백엔드는 pcm 업로드를 STT 호출 직전 RIFF/WAV로 정규화해 raw PCM을 audio/wav로 오인하던 계약 틈을 닫았다. 실제 Deepgram streaming, 물리 마이크, public WSS, 50분 양방향 실측은 계속 외부/실측 gate다. 검증: app/test_voice_ws.py 11 passed, npm run typecheck, voice-success chromium-single-run 2 passed.

87차 적용(2026-06-29): refactor-governance P3로 /voice/ws에 provider-neutral stt_result control을 추가해 EOT helper를 route 경계에 연결했다. 새 control은 text, final, silence_ms, provider_events를 받아 assess_end_of_turn()이 ready를 판단하고, pending이면 턴 실행 없이 state=listening으로 돌아간다. ready일 때만 transcript를 emit하고 _run_turn_and_speak()로 진행한다. 기존 audio_start→binary→audio_end, text_turn, ping/close 계약은 유지했다. 실제 Deepgram interim/final WSS 연결과 public WSS/물리 마이크/50분 실측은 여전히 남은 gate다. 검증: py_compile, app/test_voice_ws.py app/test_voice_service.py 27 passed.

88차 적용(2026-06-29): refactor-governance P3로 Node conformance runner의 stream event/payload 검증을 schema-driven으로 바꿨다. scripts/check-engine-gateway-contract.mjs는 더 이상 token/done/error 이벤트 목록과 payload 필드를 로컬 상수·분기로 들고 있지 않고, engine_gateway_schema.v1.jsonStreamPacket.oneOf, payload $ref, x-engine-gateway-sse-events를 읽어 golden fixture를 검증한다. raw token text fallback과 data: [DONE] compatibility line은 유지했다. 검증: node scripts\check-engine-gateway-contract.mjs --json, 당시 gateway/backend focused suite 통과, 이후 최신 L1 focused 120 passed로 재검증.

89차 적용(2026-06-29): refactor-governance P3로 SessionEvaluationWritefrom_result()/from_error() factory를 추가해 session evaluation 저장 packet 생성 규칙을 한 곳으로 모았다. routes/sessions.pyroutes/eval.py는 더 이상 status/source/scope/stage/payload/error 묶음을 직접 조립하지 않는다. 성공·평가오류는 result.to_dict()status="ready|error" 규칙을 유지하고, 실행 예외 fallback은 기존처럼 payload={}, error=str(exc)를 저장한다. 검증: py_compile, app/test_evaluation_persistence.py 12 passed, evaluation/learner/teacher focused 53 passed, backend focused 116 passed, residual SessionEvaluationWrite( route/test direct constructor 0건.

90차 적용(2026-06-29): refactor-governance P2로 M2 턴 메모리 전달 경계를 TurnMemory 값 객체로 단일화했다. recall_summary/pinned_facts/recent_turns/kb_behavior_cuesTurnContext.memory에 보관되고, prepare_turn(memory=...)에서 기존처럼 PII 마스킹된 뒤 build_turn_messages(memory=...)로 전달된다. REST/SSE/voice와 재평가 경로는 RecallContext, 최근 client-visible 턴, KB cue를 TurnMemory로 조립한다. 이는 live provider 압축이나 운영 품질 검증 완료가 아니라, Node.js 전환 전 내부 계약 표면을 줄인 구조 개선이다. 검증: py_compile, M2/voice/evaluation focused 115 passed, gateway contract 23 passed, Node conformance OK, 구 ctx.recall_summary/ctx.recent_turns 직접 접근 0건.

91차 적용(2026-06-29): refactor-governance P2로 auth managed-user 생성/재활성화 입력 경계를 ManagedUserUpsertInput dataclass로 고정했다. create_session(), 관리자 POST /admin/users, auth 회귀 테스트의 upsert_managed_user() 호출은 더 이상 email/display_name/role/admin_access/cohort_ids/user_id/external_id/affiliation/account_status/reactivate 키워드 묶음을 직접 넘기지 않고 단일 입력 객체를 만든다. 기존 ManagedUserMemoryInput은 DB→메모리 fallback/store 동기화용, ManagedUserPatch/users/me·온보딩·관리자 수정용 partial update 경계로 유지했다. 외부 API/DB schema/온보딩 정책 변경은 아니며, admin_access·super_admin 위임과 onboarding_required 선차단 동작은 기존 계약을 보존한다. 검증: py_compile, auth/admin/session focused 103 passed, 구 keyword call 0건, npm run check:api-types.

92차 적용(2026-06-29): refactor-governance P3로 현재 Python engine gateway의 응답 메타데이터 중복 선언을 줄이고, Node conformance runner가 gateway-default를 실제 provider 모델명이 아닌 default-routing sentinel로 검증하게 했다. GATEWAY_PROVIDER, GATEWAY_FALLBACK_MODEL_NAME, _response_model_name()/v1/generate/v1/streamprovider/model 반환 규칙을 함께 소유한다. scripts/check-engine-gateway-contract.mjs는 golden fixture의 request model이 schema sentinel과 같고, response/done packet model은 resolved provider model인지 확인한다. wire schema, SSE frame shape, session reuse, error event 동작은 바꾸지 않는 순수 구조 정리다. 검증: py_compile, node scripts\check-engine-gateway-contract.mjs --json OK(defaultModelSentinel=gateway-default), gateway contract 23 passed.

93차 적용(2026-06-29): refactor-governance P2로 Python engine gateway의 메시지 분해 경계를 GatewayPromptParts dataclass로 이름 붙였다. _split_messages()는 더 이상 tuple[str, str]를 반환하지 않고 system_prompt/user_payload를 명시하며, 실제로 쓰지 않던 history 조립 흔적과 주석 모순을 제거했다. 동작은 기존과 같다: system 메시지는 공백 제외 후 \n\n로 합치고, 마지막 user 발화만 이번 턴 payload로 보내며, user가 없으면 session resolution 전에 /v1/generate//v1/stream 모두 400이다. 검증: py_compile, node scripts\check-engine-gateway-contract.mjs --json OK, gateway contract 27 passed, L1 backend focused 120 passed.

94차 적용(2026-06-29): refactor-governance P2로 M2 LLM digest 후보의 local quality harness를 추가했다. DigestQualityAssessment/SessionDigestWorkerOutcomebuild_llm_digest_worker_outcome()이 빈/짧은 digest, raw forbidden substring, 내부 평가·CCD·상태 marker, 잘못된 S{session_no}: prefix를 reject하고, 통과한 후보만 SessionDigestResult(source="llm")로 승격한다. open_threads와 session/case id는 입력 계약에서 그대로 보존된다. 이는 live provider 호출, 운영 scheduler, 임상 골든셋 품질평가, 재압축 완료가 아니다. 검증: py_compile, py -3.11 -X utf8 -B -m pytest -p no:cacheprovider app/test_session_memory.py -q 20 passed.

95차 적용(2026-06-29): refactor-governance P2/P3로 M2 one-shot session digest worker 경계를 추가했다. app/services/session_digest_worker.pyCompressionJob을 Node-compatible GenerateRequest/EngineMessage로 변환하고, 주입형 engine·audit_hook으로 단발 호출한 뒤 local quality gate를 통과한 결과만 session_summary.digest/compressed_by/token_countcase_profile.case_digest에 idempotent 적용할 수 있게 한다. DB loader는 persisted fallback summary와 client-visible masked transcript만 재구성하며 raw text, evaluator-only turn, CCD, end_state는 prompt에 넣지 않는다. 이는 provider live run, background scheduler, 임상 골든셋 품질평가, 재압축 완료가 아니다. 검증: py -3.11 -X utf8 -B -m pytest -p no:cacheprovider app/test_session_digest_worker.py app/test_session_memory.py -q 25 passed, py -3.11 -X utf8 -B -m pytest -p no:cacheprovider app/test_session_digest_worker.py app/test_session_memory.py app/test_session_turn_persistence.py app/test_orchestrator_masking.py app/test_runtime_policy.py -q 82 passed.

96차 적용(2026-06-29): M2 digest worker를 운영자가 명시 실행할 수 있도록 scripts/run-session-digest-worker.py dry-run/apply runner를 추가했다. 기본은 metadata-only dry-run이고, --apply를 줘야 accepted 후보를 DB에 반영한다. runner는 load → DB release → engine generate → short apply 순서로 동작해 LLM 호출 동안 DB transaction을 잡지 않는다. digest 본문은 민감할 수 있어 --show-digest를 명시할 때만 출력한다. 이는 실 provider 장시간 운영 완료가 아니다. 검증: py -3.11 -X utf8 -B -m py_compile scripts\run-session-digest-worker.py apps\api\app\services\session_digest_worker.py apps\api\app\test_session_digest_worker.py, py -3.11 -X utf8 scripts\run-session-digest-worker.py --help, M2 focused 25 passed.

97차 적용(2026-06-29): SESSION_DIGEST_WORKER_ENABLED=false 기본값을 두고, opt-in일 때만 세션 종료 성공 뒤 digest worker를 background task로 예약한다. scheduler는 persisted fallback row를 load한 뒤 DB connection을 놓고 engine을 호출하며, accepted plan만 짧은 apply transaction으로 반영한다. 이미 compressed_by가 있는 세션은 loader에서 제외하고, apply도 compressed_by IS NULL CAS로 보호하며, fallback summary 재작성 시 compression metadata를 초기화한다. 이는 bounded/default-off scheduler 골격이며, 실 provider 장시간 운영·임상 골든셋 품질평가·재압축 정책 완료가 아니다. 검증: py_compile, runner --help, M2 focused 30 passed.

99차 적용(2026-06-29): 대시보드 SSOT drift gate를 보강했다. scripts/check-dev-dashboard-ssot.py는 현재 카드 상태(done=25, planned=0, doing=0), CHECK 잔여 0, owner board counts(block=2, decide=7, ext=2), 외부 GATE 상세 6행, 결정 탭 7행, 자유연습 F-10 결정 문구, M2 30/87 검증 수치, default-off scheduler/CAS 문구, 실 provider·임상 골든셋·재압축 GATE 보존, stale 82/25/26 passed 및 낡은 결정 8건 문구 제거를 함께 확인한다. 검증: py_compile, py -3.11 -X utf8 scripts\check-dev-dashboard-ssot.py --json PASS. 아래 적용 노트 일부는 historical snapshot이라 당시 통과 수치를 그대로 보존한다.

100차 적용(2026-06-29): 관리자·교수자 메일링 시스템 1차를 추가했다. 신규 외부 로그인 pending 계정은 account_pending_approval:{user_id}, 회기 종료 후 평가/error record 저장 완료 세션은 session_review_ready:{session_id}, 명시 테스트 메일은 admin_test_email:{uuid} idempotency key로 app.notification_event/app.notification_delivery에 큐잉된다. 테스트 메일은 대량 오발송을 피하기 위해 AUTH_SUPER_ADMIN_EMAILS 대상에게만 큐잉한다. 메일 HTML은 Vignette 톤앤매너(종이 배경, 세이지 CTA, 8px 카드)를 inline style로 유지하고, 본문에는 축어록·평가 전문을 넣지 않고 /admin/users, /teach/session/:sessionId/review, /admin 딥링크만 제공한다. NOTIFICATION_EMAIL_PROVIDER=smtpSMTP_*가 설정된 경우 실제 발송하며, GET /admin/notifications, POST /admin/notifications/process, POST /admin/notifications/test, scripts/run-notification-worker.py로 delivery 상태와 재시도를 운영한다.

101차 적용(2026-06-30): yunchan@twentyoz.kr처럼 기본 역할이 learner로 복원되는 슈퍼관리자가 타 학습자 회기 리뷰 딥링크를 열 때 session does not belong to user 403에 갇히던 문제를 고쳤다. /sessions/{id}/review는 본인 학습자 리뷰를 우선 시도하고, 실패한 경우 슈퍼관리자만 admin 감독자 읽기 주체로 재시도한다. 일반 learner와 단순 admin_access 위임 계정의 타인 회기 접근 차단은 유지한다. 프론트는 같은 403뿐 아니라 이미 teacherReview가 포함된 감독자 200 응답도 /teach/session/:sessionId/review로 정규화해 학습자 전용 사전/사후·공유 UI가 보이지 않게 한다. 검증: app/test_rbac_idor.py 11 passed, RBAC/auth/teacher focused 46 passed, supervisor route E2E 2 passed(PLAYWRIGHT_PORT=5176), npm run typecheck, npm run build.

102차 적용(2026-06-30): 관리자(role=admin)가 학습자·교수자·관리자 워크스페이스를 모두 직접 오갈 수 있도록 권한 경계를 정리했다. 백엔드는 Principal.can_access_role()role=admin이면 모든 역할 접근을 허용하고, require_role()은 super_admin뿐 아니라 admin도 허용 역할로 승격한다. 학습자 전용 경로(/consent accept/withdraw, /voice/ws)의 super_admin-only 승격 분기도 can_access_role(Role.LEARNER) 기준으로 바꿔 admin이 동일하게 통과한다. 프론트는 auth.tsxcanAccessRole()에 admin 전체 접근을 반영하고, 역할 전환 메뉴는 accessibleRolesFor() 단일 헬퍼가 소유하며, Sidebar admin 내비에 운영 홈·교수 콘솔·학습자 홈·학습·기록 진입을 추가했다. 외부 API 계약·DB schema·승인 게이트(account_status)·super_admin 권한 부여/회수 동작은 바꾸지 않았다. 검증: npm run typecheck, npm run check:api-types, py -3.11 -X utf8 -B -m pytest app/ -q 290 passed(RBAC/runtime 30 포함), admin 워크스페이스 내비 E2E 1 passed, layout 시각게이트 9/9, session-layout 4 passed.

103차 적용(2026-06-30): 소유자(윤찬) 결정 4건이 확정되어 owner 보드 결정 카드 7→3으로 줄었다. (1) s2s 2차 PoC 유지 — 캐스케이드 1차를 운영 기준으로 두면서 voice-s2s-poc.md 비교 PoC를 계속 연다(최종 채택은 비교 리포트 이후). (2) fast-loop 외부 폴백 허용 — 로컬 상주 분류기 기본 + 외부 Haiku/Solar 폴백 허용, 전송 시 데이터주권·보존·동의 가드 적용. (3) 재귀학습 fine-tuning 포함 — few-shot 자동갱신에 더해 fine-tuning까지 범위에 포함하되, 데이터셋은 동의·추가 축어록 수급 게이트가 닫힌 뒤 구성. (4) 서비스 음성 = 대체 TTS provider — 상업 라이선스가 명확한 대체 provider 채택, Higgs synthetic·dev sample provider는 개발 플래그로만 유지, 실존 인물/성우 ref 금지 유지. 네 결정 모두 방향 확정이며 구현/실측은 후속 build 항목으로 남는다. 남은 owner 결정은 평가설계(H1 κ/ICC), 운영 보존·에스컬레이션, 추가 축어록 수급 3건이다. SSOT drift 게이트(check-dev-dashboard-ssot.py)의 기대 카운트도 결정 3·decision panel 3행으로 동기화했다.

104차 적용(2026-06-30): 남은 소유자 결정 3건이 확정되어 owner 결정 7건이 전건 확정(결정 필요 3→0)됐다. (1) 평가설계 H1 — 평가자 신뢰도 κ≥0.70 & ICC≥0.75, 환각률 ≤3%, 독립표본 t-검정(α=0.05), 20명 실험/통제군 무작위 배정, 단회기 50분·3척도 pre-post 유지. (2) 운영 보존·에스컬레이션 — 헬스 raw 90일·rollup 365일, escalation 운영자 수동 승인, SLA 미표기(관측 샘플 이력 유지), 담당 그룹 자동 배정 미도입. (3) 추가 축어록 — 20건을 1개월 내 수급. 이 결정들은 설계·정책 확정이며, KPI threshold 반영·retention 기본값 적용·실제 수급/파일럿 evidence·통계해석은 후속 build/GATE로 남는다. 즉 owner 보드의 남은 항목은 모두 외부 증거·기관 의존(공개 OAuth 실 /turn, vnet DNS, 한신대 거버넌스, 임상팀 콘텐츠, 20명 파일럿·법무·물리 실측)이며, 운영 원칙상 E2E/외부 증거 없이는 DONE으로 체크하지 않는다. SSOT drift 게이트의 기대 카운트도 결정 0·decision panel 0행으로 동기화했다.

105차 적용(2026-06-30): 공개 배포 도메인(vnet.18ka.net)에서 슈퍼관리자가 관리자 페이지 전체를 블랭크로 보던 문제를 고쳤다. 원인은 권한이 아니라 vite preview 서버의 host 검증이었다. 배포는 npm run preview를 cloudflared 터널로 공개 도메인에 연결하는데, vite.config.tspreview.allowedHosts가 없어 공개 Host 요청이 Blocked request. This host is not allowed. 403 텍스트만 받아 SPA가 통째로 로드되지 못했다(에러 바운더리 부재로 전체 흰 화면). server.allowedHosts와 동일 목록을 preview에도 적용하고 기본 허용에 vnet.18ka.net/api-vnet.18ka.net/vignette.chanpaca.net를 추가했다. 검증: 재현(curl -H "Host: vnet.18ka.net" 127.0.0.1:5174 → 수정 전 Blocked, 수정 후 200+정상 index), 로컬 dev·preview에서 일반/슈퍼 관리자 전 admin·teach·learn 페이지 정상 렌더(pageerror 0), npm run typecheck. 회귀 방지로 npm run check:preview-hosts(scripts/check-preview-hosts.mjs)를 추가해 preview를 띄워 공개 Host 3종 200 허용·임의 Host 403 차단을 행동 검증한다(공개 3 allowed·unlisted blocked 통과). 공개 OAuth 실 /turn 증명은 여전히 별도 BLOCK이다.

106차 적용(2026-06-30): 대시보드 자체를 레드팀 대상으로 삼아 unit + E2E 게이트를 추가했다. scripts/test_dev_dashboard_ssot.py는 SSOT checker import/stateless/owner parser를 검증하고, apps/web/e2e/dev-dashboard.spec.ts는 local file, 127.0.0.1, localhost origin에서 필터 오순서·빠른 재시도·Enter/Space 키보드·details/tabs 토글·hash anchor·링크/이미지 무결성·desktop/mobile overflow를 확인한다. 같은 라운드에서 live public API https://api-vignette.chanpaca.net/health/personas가 Cloudflare 530/error code 1033으로 실패하는 것을 발견했고, 원인은 Docker Desktop 중단으로 vignette-dev-db가 꺼진 상태였다. Docker Desktop/DB 재기동 후 scripts/start-public-runtime.ps1를 다시 실행해 public health 200, unauth /personas 401, auth config 200, Google login start 302, watchdog healthy: engine, api, web-preview, cloudflared, public-api까지 복구를 확인했다. 검증: SSOT unit 3 OK, dashboard checker PASS, dev-dashboard E2E redteam 10 passed, backend RBAC/auth/teacher 47 passed, supervisor route E2E 2 passed, npm run typecheck, npm run build.

107차 적용(2026-07-01): 하린/P6 102분 회기의 SUPERVISOR AI 평가 실패를 실제 DB와 감사 로그로 추적했다. 종료 시각과 평가 row 갱신 시각 차이가 45초였고, 기존 회기말 평가 background task가 wait_for(..., 45.0) 하드캡에서 asyncio.TimeoutError를 맞은 뒤 str(exc)==""를 저장해 payload={}, error=""가 된 것이 원인이었다. 회기말 평가는 SESSION_EVALUATION_TIMEOUT 또는 ENGINE_TIMEOUT을 쓰도록 바꾸고, 빈 예외 문자열은 예외 타입/명시 메시지로 저장한다. 명시 재평가 API는 실패 결과를 먼저 저장한 뒤 502/503으로 노출한다. 프론트는 종료 실패 시 리뷰로 이동하지 않고 alert를 보여주며, 평가 대기 리뷰는 제한 폴링으로 ready 전환을 기다리고, 교수자 검토 화면에는 AI 평가 재시도 버튼을 추가했다. 재시도 실패 UI 유지와 저장된 평가 실패 row의 read-model 노출도 회귀 테스트로 고정했다. 모바일 코칭 모드에서 우측 컬럼 숨김 때문에 AI 튜터 말풍선이 보이지 않던 회귀도 함께 수정했다. 검증: backend 전체 297 passed, npm run check:api-types, npm run typecheck, npm run build, session-review desktop/mobile 16 passed, session-mvp desktop/mobile/single-run 5 passed, session-layout desktop/mobile 8 passed, layout-visual-gate 9 passed.

108차 적용(2026-07-01): mock E2E가 가리던 브라우저 실제 SSE 저장 경로를 추가로 검증했다. 기존 session-persistence.spec.ts는 real API/DB를 쓰지만 /api/sessions/{id}/turn를 직접 호출해 UI의 openSessionStream() → backend /streamfinalize_completed_turn() 저장 경로를 타지 않았다. 새 @single-run 테스트는 /api/health에서 db=true, engine=true를 확인한 뒤 실제 Session UI에서 회기를 시작하고, 학습자 발화를 브라우저 fetch stream으로 보내며, 입력창이 다시 enabled 될 때까지 stream 완료를 기다린 다음 GET /sessions/{id}/review에서 learner/client 저장 축어록을 확인한다. 최신 session-persistence 묶음 검증: PLAYWRIGHT_PORT=5205 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 7 passed, npm run typecheck passed, 현재 Playwright 수집 기준 162 tests in 18 files.

109차 적용(2026-07-01): 원래 E2E silent error 목표에 맞춰 AI 튜터와 위기 게이트 경로를 추가로 좁혔다. POST /sessions/{id}/live-coach는 코칭 생성 뒤 save_live_coach_event() 실패를 except/pass로 묻고 있어, 사용자는 코칭 카드를 봤지만 재로딩/이력에는 사라지는 상태가 가능했다. 라우트에서 저장 실패를 삼키지 않게 하고, 실패가 503으로 표면화되는 백엔드 회귀와 실제 Session UI의 코칭 모드 → 턴 완료 → DB-backed GET /live-coach 이력 → 재로딩 후 C 마커 E2E를 추가했다. 또한 crisis safety/done.conversation_stopped 또는 음성 reply.conversation_stopped가 빈 응답으로 끝날 때 내담자 응답 없음 신호가 안전 게이트를 덮지 않게 막고, 위기 안전 자원 109·입력 disabled·live-coach 미호출을 E2E로 고정했다. 검증: python -X utf8 -m pytest -p no:cacheprovider app/test_session_turn_persistence.py -q 27 passed, PLAYWRIGHT_PORT=5193 npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1 2 passed, PLAYWRIGHT_PORT=5199 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 5 passed, npm run typecheck passed.

110차 적용(2026-07-01): 음성 비언어 메타데이터가 fixture-only로 통과하는 silent gap을 줄였다. db.healthcheck(), runtime table readiness, deploy preflight DB mode는 이제 app.turns.provider_events 하나가 아니라 audio_ref/silence_ms/speech_rate/barge_in/provider_events 5개 컬럼 전체를 확인한다. dev/runtime bootstrap은 누락된 nullable 음성 컬럼도 보강한다. 새 DB-backed @single-run E2E는 실제 세션을 만든 뒤 /api/voice/ws stt_result control로 침묵·끼어듦·provider event를 보내고, GET /sessions/{id}/review에서 제한된 nonverbal 칩으로 파생되는지 확인한다. 검증: python -X utf8 -m pytest -p no:cacheprovider app/test_runtime_policy.py app/test_session_turn_persistence.py app/test_voice_ws.py app/test_voice_service.py -q 77 passed, PLAYWRIGHT_PORT=5199 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 5 passed, python -X utf8 -m py_compile app/db.py app/auth_sessions.py app/test_runtime_policy.py ..\..\scripts\check-deploy-preflight.py, preflight skip-db PASS.

111차 적용(2026-07-01): 실제 Session 마이크 UI가 비언어 메타를 만들지 않던 공백을 줄였다. AudioWorklet 캡처는 PCM RMS/peak로 voice activity와 trailing silence를 추정해 audio_endsilence_ms, barge_in=false, browser source provider_events를 보낸다. MediaRecorder fallback은 raw PCM을 볼 수 없어 duration 기반 voice activity만 보낸다. 리뷰 read-model은 turn-level silence_ms가 이미 침묵 칩을 만들면 provider silence 칩을 중복 노출하지 않는다. 검증: npm run typecheck, backend voice/session focused 77 passed, PLAYWRIGHT_PORT=5197 npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run --workers=1 2 passed, 최신 PLAYWRIGHT_PORT=5205 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 7 passed, 현재 Playwright 수집 기준 162 tests in 18 files.

112차 적용(2026-07-01): 교수자 회기 리뷰 데스크톱에서 overview/transcript가 여러 CSS Grid row를 가로질러 오른쪽 검토 rail 높이를 나눠 먹으며 중앙에 큰 빈 row가 생기던 문제를 제거했다. 1181px 이상 교수자 리뷰는 main column + review rail 2열 wrapper로 분리하고, main 내부는 요약 전체폭, 차트/회기 흐름 2열, 축어록 전체폭으로 배치해 서로 다른 rail 높이가 빈공간을 만들지 않게 했다. 회귀 방지로 layout-visual-gate.spec.ts에 교수자 리뷰 폭별 dead vertical gap 검증을 추가했다. 검증: npm run typecheck, npm run build, session-review desktop 8 passed, layout-visual-gate 10 passed, session-layout desktop/mobile 8 passed.

113차 적용(2026-07-01): 학습자 /learn/practice 페르소나 리스트가 긴 한글 주호소 요약에서 카드 내부를 클립하던 문제를 수정했다. .lh-list-pane의 viewport 고정 높이와 내부 스크롤을 풀고, .lh-personas/.lh-persona가 콘텐츠 높이를 그대로 만들게 했으며, .lh-persona__summary 3줄 clamp를 제거했다. learner.spec.ts에는 각 페르소나 카드의 summary/body/card scroll overflow가 0인지 재는 regression guard를 추가했다. 검증: npm run typecheck, npm run build, 브라우저 rect smoke 1095px/1440px에서 카드 overflow 0 및 문서 가로 overflow 0. 현재 로컬 127.0.0.1:8000/health가 timeout이라 real API 기반 전체 learner.spec.ts는 이번 패스에서 미실행.

114차 적용(2026-07-01): 회기 종료 후 background deep 평가가 실제 durable DB row로 저장되는지 fixture 없이 검증했다. _schedule_session_evaluation()은 생성한 task를 반환하고 done callback에서 crash/cancel을 로그로 관찰하며, 평가 결과·timeout/error 저장이 durable store에 닿지 않으면 명시 error 로그를 남긴다. 교수자 명시 재평가 API도 평가 산출물이 생성됐지만 저장 실패하면 503으로 표면화한다. GET /eval/sessions/{id}/evaluationstatus/error/durable을 노출해 캐시 착시와 DB 저장 증거를 구분한다. 새 DB-backed E2E는 학습자 세션 생성→턴→종료→teacher 로그인→/eval/.../evaluation polling→교수자 리뷰 UI 평가 완료까지 실제 API/DB/엔진으로 확인한다. 검증: API 전체 309 passed, npm run check:api-types, npm run typecheck, PLAYWRIGHT_PORT=5205 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 7 passed, PLAYWRIGHT_PORT=5203 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 16 passed, 현재 Playwright 수집 기준 162 tests in 18 files.

115차 적용(2026-07-01): 교수자 학생 분석을 /teach 대시보드에서 분리해 좌측 메뉴의 /teach/analysis 전용 작업면으로 이동했다. 교수 콘솔은 검토 큐·위기 알림·페르소나 검수·최근 회기 triage만 맡고, 학생 분석은 학습자 목록 레일, 사용자별 추이, 단계 분포, 전체 회기 타임라인, 회기 리뷰 드릴다운 구조를 맡는다. 회귀 방지로 teacher.spec.ts는 콘솔에 분석 패널이 없고 메뉴를 통해 학생 분석 페이지로 이동하는지 확인하며, layout-visual-gate.spec.ts는 학생 분석 페이지를 7개 폭에서 별도 캡처·검사한다. 검증: npm run typecheck, npm run build, npm run check:api-types, PLAYWRIGHT_PORT=5211 npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1 9 passed, PLAYWRIGHT_PORT=5214 npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1 11 passed, SSOT checker PASS, 현재 Playwright 수집 기준 163 tests / 18 files.

20차 적용(2026-06-28): 신규 Google/SAML 사용자는 account_status=pending으로 시작하고 승인 전에는 /pending 안내 화면만 본다. yunchan@twentyoz.kr는 슈퍼 관리자 allowlist로 admin+approved를 받으며, /admin/users는 가입 승인 탭에서 pending 계정을 승인 또는 보류 처리한다.

21차 적용(2026-06-28): 관리자 페이지 진입권을 기본 역할과 분리해 app_user.admin_access로 저장한다. AUTH_SUPER_ADMIN_EMAILS 기본값은 yunchan@twentyoz.kr, hoonjungkoo@hs.ac.kr이며, 슈퍼 관리자는 학습자·교수자·관리자 공간 전환과 관리자 권한 부여/회수를 할 수 있다. 학생·교수 계정도 admin_access=true면 우측 상단 관리자 진입이 노출된다. 구성 슈퍼 관리자의 권한 회수와 계정 비활성화는 차단한다.

25차 적용(2026-06-28): /admin/users에서 허용 도메인 밖 이메일도 정확한 계정 단위로 강제 등록할 수 있다. Google/SAML/dev-login은 미리 등록된 이메일만 도메인 게이트 예외로 통과시키고, provider 로그인 시 기존 관리 row의 역할·코호트·승인 상태를 이어받는다. 미등록 외부 도메인 로그인은 계속 차단한다.

10차 적용(2026-06-27): 학습자 영역을 /learn 대시보드, /learn/practice 연습 대상, /learn/history 기록으로 분리했다. 홈은 최근/진행 회기를 기준으로 Live2D 아바타·감정 힌트·마지막 내담자 반응·학습자 마지막 발화·AI 코치 문장을 보여주는 세션 리캡 중심 대시보드로 바꾸고, 새 회기 시작은 학습 탭으로 낮췄다.

11차 적용(2026-06-27): 현재 /learn 화면을 다시 캡처한 뒤 UX 원칙(사용자 필요, progressive disclosure, 정보 향, adaptive layout)을 기준으로 이미지 생성 시안 3장을 만들었다. UI 지침에는 11-learner-dashboard-redesign-concept-b.png, 12-learner-dashboard-redesign-concept-b-alt.png만 채택하고, 상담실 배경으로 나온 10-learner-dashboard-redesign-concept-a.png는 폐기했다. 구현은 회기 리캡과 AI 코치/이어하기 CTA를 홈 첫 블록으로 묶고, 독립 다음 행동 카드는 제거했다.

16차 적용(2026-06-28): 생성 시안과 더 맞추기 위해 /learn 하단을 리뷰 대기, 다음 연습 추천, 최근 기록 3분할 카드로 재배치했다. AI 코치 패널에서는 진행/리뷰 카운트와 보조 링크를 제거하고, 코칭 문장·3개 체크 힌트·단일 이어하기 CTA만 남겼다. 최신 검증 캡처는 docs/design-verification/learner-home-redesign/final-*.png다.

18차 적용(2026-06-28): /learn/history는 표형 기존 회기 목록을 제거하고 전체, 진행 중, 리뷰 필요, 보관됨 상태 필터와 검색, 카드형 회기 기록 작업대로 재구성했다. 설계 지침은 docs/design-concepts/prompts/learner-history-archive-redesign-2026-06-28.md, 검증 캡처는 docs/design-verification/learner-history-redesign/history-*.png다.

22차 적용(2026-06-28): 회기 아카이브를 실제 저장 상태로 연결했다. 학습자 종료 회기는 POST /sessions/{id}/archive/restore로 보관·복원하고, app.session_archive_state는 삭제가 아니라 학습자별 보기 상태만 저장한다. 회기·턴·리뷰·공유 링크·연구/감사 증거는 유지하며, 보관된 회기는 /learn/history보관됨 필터와 dashboard archived_sessions에 반영되고 리뷰 대기 행동 큐에서는 제외된다.

24차 적용(2026-06-28): 운영 콘솔 /admin/usage의 DB 집계 쿼리에서 ORDER BY tokens_in + tokens_out가 PostgreSQL 원본 컬럼 참조로 해석되어 prod에서 503 fail-closed 되던 문제를 수정했다. provider/model별 정렬은 SUM(tokens_in)+SUM(tokens_out) 집계식 기준으로 바꿨고, public API 프로세스의 실제 HTTP smoke는 source=database, durable=true로 200을 반환한다.

23차 적용(2026-06-28): app.persona_voice_map을 실제 /voice/ws TTS voice 선택에 연결했다. 명시 query preset이 최우선이고, 없으면 세션의 persona_id/persona_version 또는 dev persona catalog의 voice map을 읽으며, unsupported provider는 기존 persona-code fallback으로 fail-safe 처리한다. seed materializer는 기본 OpenAI voice map row를 ON CONFLICT DO NOTHING으로 생성하고, dev 런타임 스키마 보강은 기존 DB의 persona_voice_map 누락도 함께 복구한다.

12차 적용(2026-06-27): 회기 리뷰 데스크톱 레이아웃은 sticky 요약 레일을 유지하되 트랜스크립트가 같은 컬럼을 공유하지 않도록 재배치했다. 왼쪽 요약 문장은 보조 브리핑 밀도로 낮추고, 축어록은 중앙 근거 작업대에 고정해 스크롤 중 레일이 발화 내용을 가리지 않게 했다.

13차 적용(2026-06-27): 회기 리뷰의 적절한 개입 등 평가 콜아웃은 제한 markdown을 기본 렌더링한다. effective_opennesseffective openness(유효 개방도)로 학술 용어화하고, 상담자 질문 발화는 ReviewNote.quote 인용 블록으로 분리해 카드 내부에서 시각화한다.

14차 적용(2026-06-27): 활성 상담 세션은 시작 직후 실제 session_id URL로 고정해 새로고침 후에도 같은 회기 상세를 복원한다. 전역 topbar는 몰입형 화면에서 계속 숨기되, 내부 세션바에 기록으로/학습 홈 이동을 추가했다. 종료된 세션 상세도 곧바로 리뷰로 밀어내지 않고 읽기 전용 기록 화면에 머물며, 리뷰 이동은 명시 버튼으로만 수행한다.

15차 적용(2026-06-28): 학습자·교수자·운영·페르소나 스튜디오 화면을 같은 작업형 폭 정책으로 정렬했다. 교수 콘솔은 검토 대기·위기 알림·페르소나 검수·저작실 순서가 DOM과 화면 모두 일치하고, 페르소나 스튜디오는 SSOT 첨부/RAG 생성/항목 편집/검증 큐가 한 작업면에서 이어지는 구조로 유지한다. 적용 리포트와 대표 캡처는 docs/ops/layout-research-2026-06-28/dashboard-layout-alignment-report.md에 남겼다.

래스터만 사용이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.
기능 우선메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.
온화하지만 임상적종이 질감, 낮은 채도, 차분한 빛은 허용하되 상담 훈련 도구의 신뢰감을 먼저 세운다.
반응형 세트각 핵심 화면은 한 장 안에 PC·태블릿·모바일을 함께 배치해 전환 규칙을 비교한다.
화면추출한 핵심 기능디자인 의도
로그인/진입Google OAuth, 허용 도메인 안내, 로컬 역할 선택, 교육용 비치료 고지신뢰·보안이 먼저 보이는 조용한 진입 화면
학습자 홈학습 요약, 진행/리뷰 가능 회기, 마지막 세션 리캡, Live2D 감정 힌트, AI 코치, 학습·기록 드릴다운오늘 이어갈 회기와 학습 상태가 먼저 보이는 개인 대시보드
상담 세션회기 단계, 내담자 컨텍스트, 아바타, 음성 오브, 실시간 자막, 텍스트 폴백, 안전 점검화면 중심은 내담자, 피드백은 조용한 신호
회기 리뷰요약, 감정 밸런스, 회기 흐름, 축어록, 기법 태그, 개선점, 오디오/PDF점수 폭격이 아니라 근거 기반 회고
교수 콘솔리뷰 대기 큐, 코호트 현황, 페르소나 검수, 종료 회기 목록awareness보다 triage에 가까운 교수자 작업대
관리자 콘솔운영 홈: 교육 진행·온라인 사용자·헬스 샘플 이력·AI 비용·운영 티켓 요약. 하위 메뉴: 사용자 관리, 접근 권한, 운영 티켓 처리 큐. 헬스 샘플은 화면 조회와 synthetic sampler가 같은 DB 이력에 남김상태 관측과 관리 작업을 분리한 실제 운영 콘솔
설정계정, 관리자 AI 운영 설정, 지원 요청 현황, 테마, 음성 프리셋, 속도, 알림역할별 설정과 본인 지원 요청 상태만 보이는 조용한 환경 관리
이미지 요소종이 질감, 상담실 책상, 노트, 식물, 부드러운 빛, 패턴, 아바타 분위기UI 뒤에서만 느껴지는 따뜻함과 반구상 내담자 톤

상태 보드

현재 추적 중인 작업을 카드로. 요약만 노출되고, 상세 근거·검증 로그는 카드를 펼쳐야 보인다. 필터로 "내 차례"·블로커만 추려 볼 수 있다.

필터

한신대 PDF 정합성 · 이번 세션 발견

원천 기술분석 PDF(트웬티온스×한신대, 19쪽) 전수 정독 대조 (2026-06-27)
현재

PDF가 차별 기술로 꼽은 '저항·반응 조절'(AI가 너무 착하지 않게, 실제 환자처럼)·상태머신(라포→탐색→개입→정리)·RAG(자료검색)·이론모드(인간중심/CBT)·답변과 분리된 가드레일·SSE 스트리밍·React19·피드백 JSON Schema 강제(벤더중립)가 이미 구현·정렬됨. Stanford RCT(피드백 없는 연습=역효과)도 H2 턴평가로 충족 방향.

권고

이 코어를 '교육용(비의료)' 포지션으로 유지. 평가는 교수자 검토용 보조지표로 표기.

현재

라이브 테스트에서 수련생이 내담자를 "수동적 패배자"라 비난·조롱("웃음이 나오십니까")해도 민재가 공손히 흡수 → 훈련 가치 훼손. 수정: ① L0에 무례·모욕·조롱·인신공격 시 현실적 동맹 균열(위축·방어·따지기·종결의향) 지침 ② PersonaCard.triggers(역린) 필드 + CCD 핵심상처 파생 역린 블록(상담자가 핵심상처를 조롱·확정·강요·평가절하 시 가장 강한 반응). PDF '저항·반응 조절' 핵심 차별 강화. 무회귀 77/77.

잔여

P1~P3 명시 triggers는 임상팀 정밀 저작(현재 CCD 파생 기본값으로 작동). 라이브 재현 검증.

현재

PDF(9·16·18쪽): 자해·자살 감지 시 대화 중단 + 자살예방상담 109 안내 + 지도교수 자동 알림, 안전은 LLM 밖 별도 CrisisGate. 구현: CRISIS_HOTLINE_NUMBER=109, TurnResponse.crisis_resource/conversation_stopped, REST·SSE·voice 경로의 엔진 호출 전 중단, app.safety_events detail 적재, 교수자 대시보드 안전 알림 큐.

검증

pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q 20 passed, 안전 이벤트 DB insert payload 테스트 포함. pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q 21 passed.

잔여

비밀보장 예외고지·생명유지서약 등 임상 문안, 실시간 push/메일 알림, 지역 자원 확장, 운영 정책 문구는 소유자·임상팀 확정 필요. 현재 교수자 알림은 safety_events 기반 대시보드 큐다.

현재

TeacherDashboardResponse.learner_growthGET /teacher/learners/{learner_id}/analysis가 턴별 fast-loop 평가의 appropriateness, rapport_signal, 기법 태그를 학습자별로 집계한다. /teach는 검토 큐·위기 알림·페르소나 검수·최근 회기 triage만 맡고, /teach/analysis는 학습자 목록 레일, 회기별 막대 추이, 평균 라포, 변화폭, 단계 분포, 전체 회기 타임라인을 표시한다. 평가가 없는 회기는 "평가 부족"으로 남겨 가짜 곡선을 만들지 않는다. 회기 행은 /teach/session/{id}/review로 진입해 같은 회기 리뷰를 교수자 읽기 전용으로 열고, 워크시트는 학습자 제출물 수정 없이 검토 전용으로 표시한다.

검증

pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q 21 passed, pytest app/test_rbac_idor.py app/test_teacher_dashboard.py -q 9 passed, npm run check:api-types, npm run typecheck, npm run build passed. npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1 4 passed.

잔여

자기효능감·기술숙련도·수련만족도 사전사후(H1)와 교수자 코멘트/검수 워크플로 확장은 별도 평가설계·임상팀 문항 확정이 필요하다.

현재

교수자/관리자는 /sessions/{id}/review를 읽을 수 있고, 학습자 워크시트 저장은 learner 전용으로 유지한다. 교수자 검토 상태는 app.session_review_statusreview_status, review_note, reviewed_at로 저장되며, teacher dashboard 응답과 SessionReview가 이 값을 표시한다. PUT /teacher/sessions/{session_id}/review-status는 교수자 메모 저장과 검토 완료 처리를 담당하고, 검토 완료된 회기는 pending queue에서 제외한다.

검증

pytest app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_learner_dashboard.py -q 12 passed, npm run check:api-types, npm run typecheck, npm run build, teacher.spec.ts 4 passed. 교수 콘솔 검토 큐 노출과 학습자 리뷰의 교수자 전용 빈 grid 제거는 E2E/시각 게이트로 재검증했다.

잔여

임상 루브릭·최종 검수 기준은 외부 정의 전까지 완료 처리하지 않는다.

현재

교수자 성장 집계와 중복되던 점수·라포·기법 계산을 app.services.session_metrics로 공용화했다. GET /sessions/dashboard는 학습자 본인 세션만 집계해 overview, growth, persona_progress, achievements, recent_feedback를 반환하고, LearnerHome은 누적 회기·리뷰 대기·최근 평가·라포 흐름·페르소나별 진행·최근 피드백·마지막 세션 리캡을 실제 서버 데이터로 보여준다. 성취는 공식 수료/등급이 아니라 실제 연습 milestone만 사용한다.

검증

pytest app/test_learner_dashboard.py app/test_teacher_dashboard.py app/test_rbac_idor.py -q 12 passed, npm run check:api-types, npm run typecheck, npm run build, learner.spec.ts 6 passed, session-review.spec.ts 2 passed, layout-visual-gate.spec.ts 9 passed, session-layout.spec.ts 4 passed.

잔여

UX 감사 기준으로 2차에서는 카드 격자 비중을 줄이고 게이지·small multiples·테이블 중심으로 정리해야 한다. 모바일에서 임상 요약을 단순 숨김 처리하는 부분도 접힘/요약으로 바꾸는 게 맞다.

현재

① PDF: 고성능(Claude·GPT=내담자발화·실시간평가)/저비용 한국어(Solar=단순응답·사후요약) 2단계 + 캐싱 90%절감. 현재 client AI는 claude_cli 기본 라우팅, fast/deep evaluator는 EVALUATOR_FAST_MODEL/EVALUATOR_DEEP_MODEL 설정 시 모델 override를 gateway에 전달한다. evaluator structured 결과는 canonical request SHA-256 기반 인메모리 semantic cache 1차로 중복 호출을 줄이며, 원문 prompt·completion은 캐시에 저장하지 않는다. 벤더중립 인터페이스와 L0~L2 cache hint/session reuse도 유지한다. ② 백엔드 방향은 Node.js 우선 교체 가능성으로 정했다. 단, 현재 Python/FastAPI는 납품 핵심 경로라 전면 재작성하지 않고, 계약 경계를 먼저 고정해 갈아치울 수 있게 만든다. 이번 패스에서 앱 orchestrator가 raw gateway SSE line을 직접 해석하지 않도록 EngineClient.stream_packets() 경계를 추가했고, Python gateway의 /v1/generate 응답은 GenerateResponse 계약 모델로 반환한다. gateway-default는 실제 provider 모델명이 아니라 gateway 기본 라우팅 sentinel로 계약 모듈이 소유하며, FastAPI client와 Python gateway는 이 값을 explicit model override 없이 정규화한다. 추가로 app/session_read_model.pyapp/persona_read_model.py가 세션·페르소나 브라우저 응답 DTO와 builder/mapper를 소유하고, app/persona_generation_contract.py가 페르소나 draft schema/prompt/payload coercion을, SessionEvaluationWrite.from_result()/from_error()가 session evaluation write packet 생성을 소유하게 해 Node read/write API가 따라야 할 contract 경계를 분리했다.

권고

2-tier 후속은 client AI/요약 모델 정책, evaluator cache의 운영 hit-rate 관측, 장기 한도 정책으로 점진 도입한다. Node 전환은 이미 고정한 engine_gateway, structured_payload_from_response(), session_read_model, persona_read_model, persona_generation_contract, SessionEvaluationWrite 계약을 유지한다. live Node gateway endpoint conformance는 실제 Node gateway 구현이 생긴 뒤 붙이고, admin mapper cleanup은 RBAC/audit/RLS blast radius 때문에 뒤로 둔다.

현재

2026-06-27/28 기준선에서는 SEO/GEO 공유 카드, 온보딩 반영 E2E, 학습자 홈, 회기 시작/종료, 리뷰 공유 버튼, 음성 성공 경로, 레이아웃 회귀를 npm run e2e 113 passed + npm run typecheck/npm run build로 닫았다. 이후 다크 UI v2와 Node 전환 경계 보강은 현재 변경 범위에 맞춰 focused gate로 재검증한다. 최신 다크 UI v2 증거는 learner.spec.ts+session-review.spec.ts 9 passed, session-layout.spec.ts 8 passed, layout-visual-gate.spec.ts 9 passed다.

다음

남은 원천문서 갭, 임상 루브릭/최종 평가설계, public Google OAuth 실제 /turn, 배포지 운영 비밀값 주입을 순서대로 닫는다.

원천문서 갭 분석

산학협력 원천문서 5종 정독 + 코드 교차조사 (2026-06-26). 근거: source-docs-gap-analysis-2026-06-26.md · critical 3 / high 4 / medium+ 6
현재

doc1/4/5가 1급 산출물로 요구하는 워크시트 축(탐색 11항목·호소 5영역·인지삼제·1/2차 감정·보호/방해 4사분면·생물심리사회 목표)을 SessionReviewResponse.caseWorksheet로 노출한다. 서버는 저장된 축어록만 근거로 규칙 기반 초안을 만들고, 프론트 리뷰 화면은 근거 턴으로 점프 가능한 워크시트를 편집할 수 있다. 저장본은 PUT /sessions/{id}/review/worksheetapp.case_worksheet에 저장되고, 이후 GET /reviewsaved_by_learner 워크시트를 자동 초안보다 우선 반환한다. 3차에서는 임상팀 확정 루브릭을 코드 수정 없이 받을 수 있도록 data/rubrics/case-worksheet-rubric.json, case-worksheet-rubric.schema.json, app.services.case_worksheet_rubric, scripts/check-case-worksheet-rubric.py를 추가했다. 현재 rubric은 status=scaffold_only, scoring_enabled=false이고, 5개 section/28개 worksheet item key가 서버 생성 워크시트와 일치하는지만 검증한다. 64차에서는 템플릿 key source를 CASE_WORKSHEET_SECTION_SPECS/case_worksheet_template_item_keys()로 명시해 validator/CLI/test가 같은 생성 워크시트 spec을 참조한다. 73차에서는 app.session_review_status에 워크시트 수동 검수 상태·메모·시각을 붙여 교수자/관리자가 승인·수정요청·반려 결정을 남길 수 있게 했다.

검증

py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_session_turn_persistence.py app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_case_worksheet_rubric.py -q 40 passed, py -3.11 -X utf8 scripts/check-case-worksheet-rubric.py --json PASS(items_total=28, scoring_enabled=false, sha256 provenance), py -3.11 -X utf8 -m py_compile focused files, npm run generate:api-types, npm run check:api-types, npm run typecheck, npm run build, session-review.spec.ts 3 passed. 로컬 API smoke: 세션 생성→워크시트 저장→GET /reviewsaved_by_learner:local smoke saved worksheet 반환.

잔여

임상팀 확정 루브릭 콘텐츠, level anchor, 가중치, 컷오프, 예시 답안, AI 채점 적용/calibration, 승인 후 잠금·재제출 정책, 재귀학습 데이터셋 approved 연계는 후속이다. 현재 scaffold는 형식 검증과 로드 경로, 교수자 수동 판정 저장만 보장하며, 초안/저장본은 숨은 정답키나 임상 점수 없이 학습자 편집 산출물로만 취급한다.

현재

prepare_turn→state_machine.evolve에 crisis.risk_level을 ideation_observed로 전달(위기 관측 상태머신 반영). escalate=True 시 REST·SSE·voice가 엔진 호출 전 중단하고 app.safety_events에 109 resource와 conversation_stopped를 적재한다. 교수자 대시보드는 이 레코드를 안전 알림 큐로 표시한다. 안전 이벤트 insert payload는 test_record_safety_event_writes_teacher_alert_payload로 회귀화했다.

잔여(임상팀)

비밀보장 예외고지·단계적 탐색·생명유지서약 스크립트 콘텐츠 + 실시간 교수자 push/메일 + 위기탐색 누락 회기리뷰 감점.

현재

TurnContext+prepare_turn에 theory_mode, sessions/voice가 sess.theory_mode 전달, evaluator._theory_mode가 세션 이론(학습자 선택) 우선. 교수자 수동 session 재평가도 persona 기본 theory_target보다 session theory_mode를 우선하고, turn 재평가는 TurnContext.theory_mode를 채운다. build_turn_messagesTHEORY_MODE_GUIDANCE를 통해 인간중심·CBT·통합 이론 프레이밍을 엔진 메시지에 넣는다. 프론트 Session.tsx는 기본값을 persona.theory_target에서 잡되, 세션 시작 전 humanistic/cbt/integrative segmented control로 학습자가 명시 선택할 수 있다. 선택값은 POST /sessionstheory_mode로 전송된다. 검증: npm run typecheck, npm run build, C3 backend focused 31 passed, eval route focused 5 passed, 평가/알림/세션 focused 39 passed, session-layout.spec.ts desktop/mobile 8 passed, session-mvp.spec.ts single-run 1 passed, layout-visual-gate.spec.ts 9 passed.

잔여(임상팀)

CBT 이론별 단계 프롬프트 체인 콘텐츠 + 이론부합 채점 루브릭은 후속이다. 이번 UI는 임상 문안·루브릭·백엔드 enum을 확장하지 않았다.

현재

doc4가 요구한 3척도 pre-post에 대해 app.learner_prepost_measure 원장, 학습자 본인용 GET/PUT /users/me/prepost-measures, SessionReview의 파일럿 증거 원장 카드를 연결했다. 저장 shape는 measure_name(self_efficacy/skill_proficiency/training_satisfaction), timepoint(pre/post), raw/min/max score, instrument_version, item_count만 받으며, 문항 텍스트·효과크기·p-value·pass/fail은 만들지 않는다. app.services.phase3_kpi_exportscripts/export-phase3-kpi.py는 원장 row를 Phase 3 evidence root의 02-measures/prepost_measures.csv02-measures/kpi_report.json으로 산출한다. participant id는 P3-###로 가명화하고, 3척도 paired normalized mean pre/post/delta, complete/missing pair를 report에 채운다. 77차에서는 phase3_kpi_contract.py가 metric 이름·필수키·status를 소유하게 해 exporter와 checker의 계약 drift를 줄였다. report metric status는 계산된 3척도 pre/post evidence를 computed_prepost, 공식 평가설계 전이라 계산하지 않는 지표를 design_pending으로 구분한다. 화면과 report 모두 공식 효과성·성적·수료 판정이 아니라 파일럿 evidence 계산임을 유지한다.

검증

py -3.11 -X utf8 -m py_compile apps\api\app\services\phase3_kpi_contract.py apps\api\app\services\phase3_kpi_export.py apps\api\app\test_phase3_kpi_export.py apps\api\app\test_phase3_artifact_checker.py scripts\check-phase3-artifacts.py scripts\export-phase3-kpi.py, py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q 10 passed, py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_user_support_tickets.py app/test_runtime_policy.py app/test_admin_ops.py app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q 46 passed, PLAYWRIGHT_PORT=5205 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 7 passed, py -3.11 -X utf8 scripts\export-phase3-kpi.py --help, py -3.11 -X utf8 scripts\check-phase3-artifacts.py --help. 기존 API/UI 검증은 API types/typecheck, SessionReview focused E2E 3 passed, visual gate 2 passed.

잔여

평가설계·공식 문항 확정, 실험/통제군 배정, 추이 시각화, 통계 검정 종류/alpha/결측 처리, 실제 20명 파일럿 evidence와 steward/legal/IAA 검수는 아직 남아 있다.

현재

텍스트 /turn·/stream·음성 경로에서 fast-loop 턴 평가를 app.feedback_scores, app.turn_technique, app.turn_client_state, app.supervisor_comment, app.alternative_utterance로 정규화 적재 후 리뷰 조회에서 hydrate한다. 이번 패스에서 app/services/live_coach.py, data/kb/live_coaching_workbook_0615.json, data/kb/live_coaching_sources/*.json, POST /sessions/{id}/live-coach, GET /sessions/{id}/live-coach, POST /kb/live-coach/source-packs/sync, app.live_coach_events를 추가했다. 세션 UI는 코칭 모드에서 AI 코치 아바타 말풍선으로 즉시 개입하고, "근거 보기" 모달과 학습자 발화 우측 C 마커를 통해 받은 코칭 이력을 채팅 위 스크롤 오버레이로 보여준다. 저장 payload는 PII 마스킹 excerpt + 코칭 구조화 JSON이며, 공식 자료는 허가된 source pack의 version/citation/summary로 넣고 장문 원문·공식 문항을 재현하지 않는다. 같은 source pack은 app.services.source_pack_sync를 통해 evaluator 전용 RAG에 증분 색인하며, hash 변경 시 최신 active document version+1로 새 문서를 만든다. 74차에서는 로컬 source pack 캐시의 key/lifetime/무효화 경계를 명시해 live turn은 API 프로세스 snapshot을 재사용하고, 관리자 sync/CLI만 refresh=True로 repo 파일을 다시 읽게 했다. 학습자 SessionReview 데스크톱은 요약/흐름, 축어록, 평가 rail의 3열 workbench와 하단 워크시트로 재배치했고, 교수자 검토 화면과 모바일/태블릿은 기존 반응형 규칙을 유지한다.

검증

python -m compileall app -q, 과거 pytest app/test_live_coach_sources.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q 27 passed, source sync focused py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_live_coach_sources.py -q 9 passed, H2 related py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_live_coach_sources.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q 44 passed, py -3.11 -X utf8 scripts\sync-persona-sources.py --help, py -3.11 -X utf8 -m py_compile focused files, npm run generate:api-types, npm run check:api-types, npm run typecheck, npm run build, npm run e2e -- e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts 15 passed. 최신 workbench 검증: npm run typecheck, npm run build, npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1 3 passed, npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1 9 passed, 04-session-review-desktop.png 직접 확인. 로컬 DB smoke: source pack sync 4 sources/15 chunks embedded, 재호출 skipped_unchanged=4, official_suicide_risk_guidelines source filter 검색 성공.

잔여

임상팀 골든셋·라이브 코칭 루브릭 검수, 공식 지침/논문 source pack 추가와 임상 검수 상태 운영정책 보강이 남아 있다.

현재

teacher/admin 전용 /teach/personas를 3열 스튜디오로 추가했다. 좌측은 저작 흐름·검수 큐·공개 카탈로그, 중앙은 RAG 자료 등록과 개요/임상/저항/회기/말투/안전/프롬프트 항목형 편집 탭, 우측은 검증·생성 근거·승인/반려 결정이다. 교수 콘솔은 진입점과 triage만 맡고 저작 본체는 스튜디오가 맡는다. 2026-06-27 UI 정리에서 중복 네이티브 파일 입력을 커스텀 SSOT 첨부 드롭존으로 교체하고, 저작 스테퍼·탭별 작성 가이드·검증 dot 정렬을 보강했다. 2026-06-28 항목형 목록 UI 정리에서 자동사고, 회기 시나리오, 말투 filler/verbal tic/nonverbal cue, 역린·금기 응답·금기어를 행 추가/삭제 UI로 전환했고, API payload는 기존 배열 schema를 유지하며 빈 항목만 저장 직전에 제거한다. 프롬프트 탭은 raw JSON textarea 대신 L1 카드·인적 범주·임상 배경·말투·수치 파라미터·역린·회기 시나리오·추가 계약 섹션을 라벨형 검토 UI로 보여주며, 런타임 prompt bundle과 API 계약은 변경하지 않는다. 레이아웃 근거와 생성 시안은 docs/ops/layout-research-2026-06-27/persona-dashboard-layout-guideline.md에 묶었다. POST /personas/sources는 첨부/붙여넣기 자료를 PII 마스킹 후 kb.raw_source_artifact에 raw hash-only 격리 증거를 남기고, 별도 sanitized 파생본만 kb.source/document/chunk에 evaluator 전용(visible_to=['evaluator'], sensitivity=2) 근거 문서로 등록한다. rag.index_document()sensitivity=3 또는 raw source marker 청크를 DB 접근 전에 차단해 raw 원문이 kb.chunk/embedding/FTS에 들어가지 않게 한다. POST /personas/drafts/generatesource_id 기반 RAG 검색 결과만 엔진에 넘기고, draft source_provenance와 engine metadata에 source id·doc/content hash·chunk id·prompt bundle id/version/hash(persona-draft-rag@2026-06-28.1)를 남긴다. 승인된 카드만 학습자 catalog에 노출되는 경계는 유지하되, P1~P7 시스템 페르소나는 DB 저작 카탈로그의 초기 부트스트랩으로 승격했다. 교수자는 공개 목록의 수정으로 승인본을 같은 persona_id의 다음 버전 draft로 복제해 편집하고, 삭제는 기존 회기 FK를 지키기 위해 같은 code family를 archived로 보관 처리한다. 기동 materializer는 누락분만 insert하고 기존 편집/archived row를 덮어쓰거나 되살리지 않는다. scripts/materialize-persona-seeds.py는 같은 seed manifest를 dry-run으로 보고하고, --apply일 때만 DB pool을 초기화한 뒤 기존 idempotent materializer를 실행한다. repo-managed source pack은 scripts/sync-persona-sources.py와 공용 sync service로 active content_hash/document version을 비교한다. 이번 패스에서 app/persona_read_model.py가 공개 카탈로그·검수 큐·초안 상세·source/evidence 응답 DTO와 mapper를 소유하고, app/persona_generation_contract.py는 draft structured schema, prompt bundle id/version/hash, GenerateResponse payload extraction, generated draft coerce/default fallback을 소유한다. routes/personas.py는 auth/DB/RAG/LLM side effect, evidence 조회, provenance 조립, HTTP error mapping을 유지한다.

검증

python -B -m py_compile app/services/rag.py app/routes/personas.py app/routes/kb.py app/test_persona_review.py app/test_live_coach_sources.py, python -B -m pytest -p no:cacheprovider app/test_persona_review.py app/test_live_coach_sources.py -q 38 passed, py -3.11 scripts\materialize-persona-seeds.py --json dry-run manifest_count=7, py -3.11 scripts\sync-persona-sources.py --help. 기존 PersonaStudio 공개본 수정/삭제 E2E: npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1 6 passed, layout-visual-gate.spec.ts chromium-single-run 8 passed, session-layout.spec.ts desktop/mobile 8 passed. DTO/mapper/생성 계약 분리 검증: py -3.11 -X utf8 -B -m py_compile app/persona_generation_contract.py app/persona_read_model.py app/routes/personas.py app/test_persona_generation_contract.py app/test_persona_review.py, py -3.11 -X utf8 -B -m pytest -p no:cacheprovider app/test_persona_generation_contract.py app/test_persona_review.py -q 39 passed, 이후 Node/gateway focused suite 120 passed, Node conformance OK, npm run check:api-types, npm run typecheck. 항목형 목록/프롬프트 검토 UI 검증: npm run typecheck, npm run build, npm run check:api-types, npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1 8 passed, npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1 9 passed, npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 8 passed.

잔여

암호화 blob/vault 기반 원문 실저장, 임상팀 최종 검수/서면 evidence, 루브릭·이론 콘텐츠 외부화는 후속이다. 임상팀이 제공하지 않은 페르소나 내용을 최종 승인본처럼 확정하지 않는다.

현재

정규식 폴백에 한국어 고위험 패턴 추가: 구체 날짜(2001.4.18→[DATE])·금액(1200원→[MONEY])·행정구역 주소(서울시 강남구 역삼동→[ADDR])에 더해, 이름/성명 라벨·성씨+이름+조사/호칭·대학교/학과/병원/센터 등 suffix 기반 한국어 NAME/ORG 로컬 휴리스틱 1차를 추가했다. 이번 패스에서 app.services.pii_masking_eval, data/privacy/pii-masking-ko-fixtures.json, scripts/evaluate-pii-masking.py를 확장해 합성 fixture 16개로 NAME/ORG/PHONE/EMAIL/RRN/NUMID/DATE/MONEY/ADDR/negative-control을 source/category/severity metadata와 함께 entity recall·forbidden substring removal·unexpected entity violation으로 평가한다. data/privacy/pii-masking-eval-input.schema.jsondata/privacy/pii-masking-eval-report.schema.json은 Node.js 전환 후에도 공유 가능한 입력/리포트 계약이며, 기본 리포트는 evidence_text_included=falsemasked_text/forbidden_remaining 원문 증거를 제외한다. fixture가 잡은 소속/안내/이름진로는 NAME 오탐은 보정했고, 66차에서는 자연 발화형 이름 라벨·자기소개 케이스를 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. Presidio가 설치돼도 한국어 누락을 막기 위해 regex fallback을 후단에 한 번 더 태운다. 70차에서는 guardrail.mask_pii() 내부에 선택형 한국어 PII recognizer adapter 경계를 추가했다. adapter는 명시 등록 전에는 비활성이고, 실패해도 예외를 밖으로 내보내지 않으며, fake adapter 테스트가 adapter span 마스킹 뒤 후단 regex fallback이 전화번호를 계속 처리하는지 고정한다. 상담 생성(generate/stream)·fast/deep 평가의 외부 LLM 호출 직후 audit.llm_call_log에 provider/model/token/cost/inference_geo/latency만 적재한다. prompt/completion 본문은 저장하지 않는다. fast evaluator prompt의 내담자 응답과 DB에 저장되는 client turn text_masked도 마스킹본을 쓰게 보강했다. 로컬 dev-login 실제 /turn smoke에서 audit.llm_call_log가 8→11로 3행 증가했다. 이번 패스에서 app_user에 이름·소속·학과·학년/직위·연락처·주소/수령지·닉네임·자기소개·아바타 URL·약관/개인정보 동의 시각·버전을 추가했다. 로그인 후 onboarding_completed_at이 없으면 전역 OnboardingGate/onboarding 외 URL(/learn, /settings, /admin, /dev/avatar-preview, /login 포함)을 모두 온보딩으로 돌리고, 온보딩 중에는 공용 셸 메뉴 없이 가입 직후 사용자 정보 입력 폼만 보여준다. learner 회기 시작도 온보딩 완료 전에는 막는다. /users/me/avatar는 PNG/JPG/WebP MIME·시그니처·3MB 제한 후 파일을 USER_UPLOAD_DIR/profile-avatars에 저장하고 URL만 보관한다. 약관·개인정보 처리방침 초안은 /users/legal-docs에서 제공한다. 관리자/교수자 권한은 온보딩에서 받지 않고 allowlist/관리자 사용자 관리로만 부여한다. 검증: py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_pii_masking_eval.py app/test_orchestrator_masking.py app/test_evaluation_persistence.py app/test_session_turn_persistence.py -q 47 passed, py -3.11 -X utf8 scripts/evaluate-pii-masking.py --json 16/16 pass, 최신 보정 검증 py -3.11 -X utf8 -m pytest app/test_orchestrator_masking.py app/test_pii_masking_eval.py app/test_session_turn_persistence.py app/test_voice_ws.py -q 59 passed, py -3.11 -X utf8 -m py_compile apps/api/app/services/guardrail.py apps/api/app/services/pii_masking_eval.py apps/api/app/test_pii_masking_eval.py apps/api/app/test_orchestrator_masking.py scripts/evaluate-pii-masking.py, 과거 온보딩/동의 검증은 pytest app/ -q 178 passed, pytest engine_gateway/ -q 11 passed, npm run check:api-types, npm run typecheck, npm run build, auth E2E 7 passed, 최종 PLAYWRIGHT_PORT=5174 npm run e2e 113 passed.

잔여

한국어 이름/기관과 contact/national/quasi-identifier 합성 technical dry-run은 16-case summary-only harness 및 선택형 ko recognizer adapter 배선 회귀까지 완료했지만, 실제 운영 모델/provider 선정, 운영 말뭉치 기반 오탐/미탐 평가, 미성년/guardian 및 법무 검토가 필요한 최종 서명 동의서·개인정보 처리방침·약관 evidence는 외부/후속 게이트다. 공개 Google OAuth 실제 /turn proof는 별도 운영 게이트.

현재

M1: voice learner turn의 audio_ref, silence_ms, speech_rate, barge_in을 리뷰 API nonverbal 이벤트로 파생하고, 회기 리뷰 축어록에 침묵·발화 속도·끼어듦·음성 입력 칩으로 표시한다. 2차로 provider_events JSONB 보존 슬롯을 추가해 control/STT provider 이벤트를 allowlist·size limit 후 저장한다. 3차에서는 저장 이벤트에 내부 taxonomy event_type/category를 추가해 sigh/cry/laugh/voice_activity/noise 등 canonical key를 남긴다. 4차에서는 인증된 회기 리뷰에 한숨·울음·웃음·호흡·운율·배경소음 계열만 한글 label/detail 칩으로 파생 노출한다. raw transcript/text payload, provider/source/raw type, 공개 공유 카드 노출은 제외했다. 실제 provider 기반 한숨·울음 감지와 live 마이크/WSS 장시간 실측은 아직 별도다. M2: case_profile을 learner-persona 안정 case로 upsert하고, session_no를 트랜잭션 안에서 원자 증가시킨다. 세션 종료 시 마스킹된 client-visible 축어록으로 fallback session_summary.digest와 case-level case_digest를 갱신하고, rapport_trajectory/alliance_level을 결정론 상태 수치에서 갱신한다. 종료 시점에는 마스킹된 client-visible 발화에서 [NAME]/[ORG] identity와 명시적 상담 약속만 pinned_fact로 upsert한다. 삽입 또는 값 변경 시에는 pinned_fact_history에 append-only 이력을 남기고, 같은 값 재확인과 locked fact는 이력을 늘리지 않는다. 5차에서는 명시적 상담 약속 철회/부정 문장만 기존 non-locked agreement:counseling fact를 contradicted로 격리하고 history reason contradiction을 남긴다. 새 contradicted fact를 임의 생성하거나 관계갈등·위기·임상 추론을 자동 모순 처리하지 않는다. 6차에서는 종료 저장 성공 뒤 background task로 마스킹된 client-visible 내담자 발화만 app.turn_embedding에 idempotent 색인한다. 7차에서는 시작 경로가 seed recall을 cache에 적재하고, REST submit/SSE stream은 공통 _prepare_turn_context()에서 recall, pinned fact, 최근 client-visible 턴, KB cue를 TurnContext/EngineMessage로 조립한다. route-level 테스트는 DB case_digest·직전 session_summary·client-visible pinned_fact가 다음 턴 L2/L4 블록에 들어가고 raw 이름이 [NAME]으로 마스킹되는지 고정한다. 76차에서는 SessionDigestInput/SessionDigestResult/SessionSummaryWrite로 종료 digest 입력·fallback 결과·DB write 인자 경계를 명시했다. 94차에서는 DigestQualityAssessment/SessionDigestWorkerOutcome로 LLM digest 후보의 local quality harness를 추가해 빈/짧은 digest, raw forbidden substring, 내부 평가·CCD·상태 marker, 잘못된 세션 prefix를 fallback 유지 대상으로 판정한다. 95차에서는 session_digest_worker.pyCompressionJobGenerateRequest 변환, 주입형 engine/audit 호출, accepted-only session_summary/case_profile 적용 계획과 one-shot DB loader를 소유하게 했다. 96차에서는 scripts/run-session-digest-worker.py가 명시 session id dry-run/apply runner를 제공한다. 97차에서는 SESSION_DIGEST_WORKER_ENABLED=false 기본값의 세션 종료 scheduler 골격과 compressed_by IS NULL loader/apply CAS를 추가했다. 입력 후보는 client-visible masked turn과 open thread만 받으며, 종료 상태 수치·평가 payload·CCD·raw text는 압축 prompt에 넣지 않는다. digest_pending은 CompressionJob 생성 여부 신호로 유지한다. M3: 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:) 기반으로 저장한다.

권고

M1 실제 provider 기반 한숨·울음/억양 감지, 장시간 마이크/WSS 실측과 역량 지표화 정책, M2 관계/임상 fact 승격 기준·실 provider 장시간 운영·임상 골든셋 품질평가·재압축, M3 운영 SAML 서명검증·기관 claim schema/test tenant·deprovisioning audit는 후속이다.

현재

X1: scripts/export-recursive-dataset.pyapp.services.dataset_export로 masked-text JSONL dry-run, PII scan, κ/ICC 계산, approved export 게이트를 구현했다. 기본은 technical_dry_run이고 ds.* write는 --write-dataset 명시 시에만 수행한다. X2: app.turns의 provider/model/tokens/cost를 최근 7일 기준으로 집계하는 GET /admin/usage를 추가했고, 운영 콘솔 /admin에 누적 비용·입출력 토큰·계량 커버리지·provider/model breakdown, 일별 daily_cost 추이, ADMIN_USAGE_BUDGET_USD 기반 예산 경고(ok/warn/exceeded)를 표시한다. EVALUATOR_FAST_MODEL/EVALUATOR_DEEP_MODEL 설정 시 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달하고, 비워두면 기존 default 라우팅을 유지한다. fast/deep evaluator structured 결과는 canonical request SHA-256 키의 인메모리 semantic cache로 재사용한다. 캐시는 EVALUATOR_SEMANTIC_CACHE_ENABLED, EVALUATOR_SEMANTIC_CACHE_TTL_SECONDS, EVALUATOR_SEMANTIC_CACHE_MAX_ENTRIES로 제한하며, 원문 prompt·completion은 저장하지 않고 성공 파싱 결과만 캐시한다. /admin/usage는 evaluator cache enabled/entries/hits/misses/stores/evictions/requests/hit_rate도 함께 반환하고, 관리자 AI 비용 카드가 hit-rate와 일별 비용/턴 추이를 표시한다. app.services.usage_reportscripts/report-ai-usage.py는 같은 usage JSON에서 provider/model별 cost share, token share, cost/turn, cost/1k tokens, budget/cache warning을 산출한다. DB 미가용 dev에서는 runtime store로 fallback하지만 prod에서는 fail-closed한다. L1: 소유자 결정으로 Node.js 우선 교체 가능성 방향을 채택했다. docs/decisions/backend-node-transition.md에 FastAPI 유지 사유와 strangler 전환 원칙을 기록했고, app/contracts/engine_gateway.py를 추가해 Python client와 현재 gateway가 같은 요청/응답/SSE 계약을 공유한다. 이어서 EngineClient.stream_packets()와 gateway GenerateResponse 반환으로 서비스 레이어의 wire-format 의존을 줄였다. engine_gateway_contract.v1.json golden fixture와 engine_gateway_schema.v1.json schema artifact로 generate request/response와 stream token/done/error packet을 고정했고, scripts/check-engine-gateway-contract.mjs가 같은 artifact를 Node.js에서 Python import 없이 검증한다. 78차에서는 gateway-default 기본 라우팅 sentinel과 모델 override 정규화를 같은 계약 모듈로 올려 FastAPI client, Python gateway, stream audit/done fallback, 관리자 기본 config가 같은 의미를 공유하게 했다. 79차에서는 structured_payload_from_response()가 structured field 우선, fenced JSON, embedded JSON object fallback을 소유해 evaluator/live-coach/persona draft의 legacy generate response 해석을 같은 계약 경계로 모았다. 80차에서는 app/persona_generation_contract.py가 persona draft schema, prompt bundle, payload extraction, generated draft coercion을 소유하게 했고, 81차/89차에서는 evaluator/live-coach가 structured payload 계약 helper를 직접 호출하며 SessionEvaluationWrite.from_result()/from_error()가 session evaluation write packet 생성을 소유하게 했다. app/session_read_model.py는 세션 목록·대시보드·상세·리뷰·공유 payload의 DTO와 builder를 route에서 분리했고, app/persona_read_model.py는 페르소나 catalog/review/draft/source/evidence DTO와 mapper를 route에서 분리했다. 각 route는 auth/RLS DB read/persistence/RAG/LLM side effect를 유지한다.

권고

X1 approved export·골든셋 승격은 steward/legal 승인, reviewer disposition, IAA 게이트(κ≥0.6/ICC≥0.75) 이후만 진행한다. X2 후속은 자동 차단·한도 enforcement 정책이다. L1의 남은 게이트는 신청서/저작권 등재 문서에 FastAPI 유지 사유와 Node 전환 계획을 반영하는 외부 거버넌스 증거다. H3 항목형 목록 저작 UI와 프롬프트 미리보기 de-JSON은 60~61차에서 처리됐다. 실제 운영 말뭉치 기반 H4 평가는 외부 데이터·reviewer 정답 라벨·법무/guardian evidence 전까지 gate로 남기고, 내부 후보였던 합성 fixture 확장과 운영 평가 입력/report schema 준비는 62차에서 technical_dry_run으로 완료했고, 실제 운영 평가는 여전히 gate다.

즉시 착수 가능(내부 코드, 외부 합의 불요)

  • C1은 저장형 워크시트와 외부 루브릭 schema/loader/validation scaffold까지 3차 완료. C2는 1차 구조 완료, C3은 이론모드 배선과 명시 선택 UI까지 2차 완료했고 수동 session/turn 재평가도 세션의 theory_mode를 우선하도록 회귀화했다. H2는 라이브 코칭 1차와 source pack version sync 및 학습자 리뷰 3열 workbench, H3은 페르소나 저작 CRUD/RAG 생성/seed materializer runner/source pack sync runner/raw source hash-only isolation/항목형 목록 저작 UI/프롬프트 미리보기 de-JSON까지 완료. 후속은 임상 스크립트, CBT 체인, 루브릭·이론 콘텐츠 외부화처럼 데이터·정책 고도화 영역이다.
  • H4(부분): 한국어 날짜/금액/주소 및 이름/기관 로컬 휴리스틱 마스킹, 16-case 합성 fixture 평가 harness와 입력/report schema, metadata-only LLM call audit 경로, 로컬 live row proof, learner 동의 수락/철회/회기 시작 하드게이트 골격은 완료. 실제 운영 말뭉치 기반 오탐/미탐 평가는 외부 데이터·reviewer 라벨·법무/guardian evidence가 필요하므로 gate로 남긴다. 내부 후보였던 합성 fixture 확장과 운영 평가 입력/report schema·summary-only dry-run 준비는 62차에서 완료했고, 자연 발화형 이름 누수 보강은 66차에서 완료했다. 70차에서는 선택형 ko recognizer adapter 인터페이스와 fake/failure 회귀를 추가해, 실제 모델/provider를 고르기 전에도 같은 mask_pii() 경계에서 adapter→regex fallback 순서를 검증할 수 있게 했다. X1 dry-run export 1차와 X2 비용 관측·예산 경고/evaluator 모델 override·semantic cache·hit-rate 관측·일별 비용 추이·모델별 비용 검증 리포트는 완료했고, 자동 차단·한도 enforcement는 정책 결정 전까지 보류한다. M1 provider_events 보존·taxonomy·인증 리뷰 파생 칩 4차, M2 case memory+pinned_fact 보수적 실적재·append-only history·명시적 상담약속 철회 contradiction·episodic embedding writer·다음턴 주입회귀 7차/digest contract + local quality harness + one-shot worker/runner/default-off scheduler 경계, M3 claim 1차, L1 schema-backed engine gateway golden fixture·default-routing sentinel·structured payload parser·Node.js conformance runner·session/persona read-model 분리는 완료. 운영 SAML 서명검증은 기관 IdP 의존이다.

소유자 결정 / 외부(임상팀·기관) 의존

  • 임상팀 산출물: C1 루브릭 항목·척도·가중치·anchor 예시·컷오프·검수 기준, C3 CBT 콘텐츠·체인, C2 위기 스크립트·서약 문안, H2 골든셋 코딩.
  • 소유자 평가설계: H1 실험/통제군·3척도 문항·50분 흐름, κ/ICC·환각률 목표(doc4 미명시).
  • 기관(한신 IT): M3 SSO IdP·test tenant·claim 스키마·인증서, 거버넌스 증거.
  • 거버넌스: L1 Node 전환 방향은 결정됨. 남은 것은 FastAPI 유지 사유와 계약 우선 전환 계획의 저작권 등재 문서화·IP 협의.

판독 한계 (정직성)

  • 핵심 축어록 2단 표는 PDF 시각 판독이 1차 근거(txt 일부 누락), OCR 잔재 존재.
  • doc4 κ/ICC·환각률은 신청서 본문 미명시 — 계약 확정 지표로 단정 금지.
  • '현재상태'는 grep/코드 사실 기반에서 시작했으나 C1·C2·C3 2차 UI·H1~H4·M1 provider_events 보존/taxonomy/인증 리뷰 파생 노출·M2 case memory/pinned_fact/history/명시철회 contradiction/episodic embedding writer/digest contract/local quality harness/one-shot worker/runner/default-off scheduler boundary·M3·X1·X2·L1 Node conformance/default-routing sentinel와 session/persona read-model 분리는 구현 후 재검증 완료 기준이다. M2는 fallback digest, digest_pending 응답 계약, LLM 후보 local quality harness, accepted-only one-shot worker/runner/default-off scheduler 경계까지이며, 실 provider 장시간 운영·임상 골든셋 품질평가·재압축은 완료로 보지 않는다. H3 원문 격리는 hash-only artifact와 RAG index 차단까지이며, 암호화 blob/vault 원문 실저장은 아직 후속이다. 실제 provider 기반 한숨·울음 감지, H4 legal/guardian evidence, M3 운영 IdP, L1 외부 거버넌스는 아직 게이트다.

코드품질 · 로컬 개발 스택

리팩토링 연구 + 1-커맨드 dev 스택 (2026-06-26~27). code-quality-research-2026-06-26.md · local-development.md
현재

게이트웨이(9099 claude_cli)+API(8000, DB 미가용 시 in-memory degraded)+웹(5173)을 커맨드라인 기준 정리 후 결정론적 기동(--reload 워처 불안정 회피, uvicorn 있는 python 자동 해석). Docker가 사용 가능하면 vignette-dev-db healthcheck/pg_isready/role safety를 점검하고, compose infra\.env 필수값 누락도 경고한다. -NoGateway/-NoWeb는 해당 컴포넌트 정리도 건너뛰고, API 정리는 지정 -ApiPort만 대상으로 한다. dev-login + seed 페르소나. 로그 .devlogs/(gitignore). 검증: 3포트 health OK + 실제 AI 턴 생성·회기리뷰 동작, API-only 재기동 후 8001/8010/5173/9099/20241 보존.

비고

AI 턴은 로컬 claude CLI 사용(사용량 발생). 진입점 http://localhost:5173. dev-down.ps1은 기본적으로 DB 컨테이너를 보존하고, -Db 지정 시 함께 중지한다. 기존 vignette-dev-db는 accepting connections이나 healthcheck가 없고, vignette_app은 NOBYPASSRLS로 존재한다. 이번 패키징 smoke에서 fresh compose DB는 owner/app role 분리 상태로 API healthy까지 통과했고, 기존 dev DB의 운영 role 전환은 별도 owner migration 확인 후 진행한다. 주의(무DB 한정): DB 없이 degraded면 페르소나가 source:seed_fallback·degraded:true라 프론트 isUsablePersona가 막아 "회기 시작" 버튼이 비활성 → UI 세션 시작엔 실제 Postgres(127.0.0.1:55432)가 필요.

현재

직접 WS 재현으로 근본원인 규명: 키 없으면 백엔드가 degraded+1011 즉시 close → 마이크 깜빡·녹음 미도달. 현재 로컬 /voice/healthavailable:true이고, 실제 P2 회기에서 MP3 발화를 /voice/ws로 보내 STT transcript 52자 → client reply 105자 → TTS 바이너리 66 chunks/266,496 bytes를 수신했다. 이 증거는 로컬 캐스케이드 검증이며, 물리 마이크·public WSS·50분 장시간 실측 완료를 뜻하지 않는다.

부수 수정

① 미설정 시 마이크 버튼 사전 비활성+명확 안내 ② TTS 지문 제거: (고개 끄덕)·(한숨)·[침묵] 등 무대지시를 음성으로 안 읽음(speakable_text, 자막엔 유지) ③ 아바타 애니메이션: 텍스트 턴 speaking 시 입 정지(0) 버그 수정 + thinking 표현 보강 ④ 브라우저 자동재생 차단 대응: 마이크 클릭 시 AudioContext.resume()으로 unlock하고 TTS Blob을 Web Audio buffer source로 재생, 실패 시 기존 <audio> fallback ⑤ AudioWorklet-first PCM16 capture와 EOT stt_result route wiring. typecheck+voice테스트+voice E2E+local live WS smoke PASS.

증상

라이브 회기에서 내담자(민재)가 "저는 Claude Code인데… Vignette 코드베이스… git status…"로 캐릭터를 깨고 개발 맥락 노출. 원인: ENGINE_MODE=claude_cli가 로컬 claude(=Claude Code)를 호출하는데 게이트웨이가 페르소나를 --append-system-prompt로 코딩 어시스턴트 정체성에 덧붙이고, cwd=repo라 CLAUDE.md·git status·env가 시스템프롬프트에 포함됨.

수정·검증

engine_gateway/gateway.py: --append-system-prompt--system-prompt(페르소나만 교체 주입) + --exclude-dynamic-system-prompt-sections(cwd/env/git/메모리 섹션 제거). 라이브: 깨던 입력 그대로 → 민재 완벽 인캐릭터, 누출 0. 게이트웨이 11 passed + 전체 API 178 passed.

현재

app/turn_runtime.py를 추가해 load_owned_session, record_completed_turn, append_completed_turn, update_session_state, record_safety_event를 공통화했다. 이번 패스에서 finalize_completed_turn을 추가해 record_completed_turn + record_safety_event 호출쌍을 REST submit/stream과 voice WS 라우트에서 제거했다. stream fast-loop 평가 누락, voice 세션 recall cache, visible_to/evaluator turn 분리는 보존.

검증

pytest app/test_session_turn_persistence.py app/test_voice_ws.py engine_gateway/test_gateway_model.py app/test_phase3_artifact_checker.py app/test_dataset_export.py -q 40 passed.

잔여

더 큰 finalize_turn/SessionRepository 경계, 장기적으로는 세션 저장소 인터페이스 SSOT화가 남아 있다.

현재

apps/api/scripts/export-openapi.pyapps/web/scripts/generate-api-types.mjs로 FastAPI OpenAPI를 apps/web/src/lib/api.gen.ts에 생성한다. npm run check:api-types가 재생성 누락을 잡는다. MeResponse, AuthConfigResponse, ConsentResponse, PersonaSummary, PersonaReviewSummary, admin/user/engine DTO, review leaf DTO, CrisisResourceResponse, teacher safety/growth leaf DTO, LearnerSessionsResponse, LearnerDashboardResponse, SessionReviewResponse, worksheet aggregate, TeacherDashboardResponse, TeacherSessionSummary, TeacherLearnerGrowth에 이어 SessionStartResponse, SessionDetailResponse, SessionDetailTurnApiSchema<...> alias로 전환했다. 세션 응답 stage는 backend StageLabel enum으로 고정해 OpenAPI가 라포|탐색|개입|정리 union을 생성한다. 69차에서는 리뷰 도식 ReviewPhaseSegment.keyrapport|explore|intervene|closing, ReviewPhaseSegment.labelSessionReviewResponse.reachedPhaseStageLabel로 좁혔다. 평가 조회 EvaluationSummary.stage는 평가 미생성 시 null, 평가 트리거 응답 stage와 교수 콘솔 TeacherSessionSummary/TeacherGrowthPoint stage는 StageLabel union으로 생성된다. 71차에서는 공개 공유 응답 PublicSessionShareResponse.reachedPhase와 live-coach 이력 LiveCoachEvent.stageStageLabel | null 계약으로 축소했다. 72차에서는 app/stage_contract.pyStageLabel, ReviewPhaseKey, legacy stage code 정규화, unknown→null 판단, 리뷰 phase key 매핑을 소유하게 해 turn_runtime.py, session_read_model.py, routes/eval.py의 중복 규칙을 제거했다. generated optional/default 차이는 화면 form state, notification default helper, 배열 렌더링 fallback, live-coach 단계 미상 표시로 흡수한다. .github/workflows/api-contract.yml은 PR/master push에서 같은 stale check를 실행해 OpenAPI type drift를 CI에서 막는다.

검증

py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_evaluation_persistence.py app/test_evaluator_model_routing.py app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_session_turn_persistence.py -q 50 passed. 공유/live-coach 후속 검증: py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_session_share.py app/test_live_coach_sources.py app/test_session_turn_persistence.py app/test_orchestrator_masking.py -q 43 passed. Stage contract SSOT 검증: py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_stage_contract.py app/test_session_share.py app/test_session_turn_persistence.py app/test_evaluation_persistence.py app/test_teacher_dashboard.py -q 42 passed. py -3.11 -X utf8 -m py_compile apps/api/app/stage_contract.py apps/api/app/turn_runtime.py apps/api/app/session_read_model.py apps/api/app/routes/eval.py apps/api/app/routes/sessions.py apps/api/app/routes/share.py apps/api/app/services/live_coach.py, npm run check:api-types, npm run typecheck, npm run build passed. 과거 영향 범위 E2E: learner.spec.ts + session-review.spec.ts + teacher.spec.ts + layout-visual-gate.spec.ts 32 passed.

잔여

남은 자유 문자열 stage는 evaluator 내부 payload, DB 저장 컬럼, dataset export, runtime store 같은 저장·분석 payload다. 라벨/phase key 변환 규칙은 app/stage_contract.py 하나로 이동했고, 브라우저-facing OpenAPI 응답 stage 계약은 현재 확인된 session/review/eval/teacher/share/live-coach 표면에서 enum/null로 고정했다.

현재

apps/web/index.html에 canonical, description, Open Graph/Twitter Card, JSON-LD(WebApplication)를 추가했고, public/robots.txt, sitemap.xml, llms.txt로 공개 사이트와 AI 검색용 설명을 정리했다. 인증 내부 경로는 robots에서 차단한다. 학습자 회기 리뷰는 종료 후 POST /sessions/{id}/share로 공개 토큰을 만들고, GET /share/session/{token}이 서버 렌더 HTML/OG/JSON-LD를 반환한다. app.session_share_link에는 토큰 해시와 sanitized preview payload만 저장하며 원문 축어록·학습자 식별자는 저장하지 않는다.

검증

pytest app/test_session_share.py app/test_session_turn_persistence.py -q 21 passed, python -m pytest app/ -q 178 passed, python -m pytest engine_gateway/ -q 11 passed, npm run check:api-types, npm run typecheck, npm run build passed.

판정

공개 제품 페이지는 색인 가능, 회기 공유 페이지는 교수자 전달용 unfurl 카드라 noindex와 API robots Disallow: /share/를 유지한다. 실제 공개 도메인 unfurl 검증은 배포 후 1회 필요하다.

현재

LogHook/log_hook(caller 0) 타입·파라미터·분기·export 제거 · 게이트웨이/엔진클라 tier dead-contract 제거(write-only, gateway는 model만 사용). 잔여(RMS 힌트·Live2D 고아 자산·파라미터객체 init_state/prepare_turn/upsert)는 docs/ops/refactor-rag-patches-2026-06-26.{md,json}에 스테이징(단계 적용).

참고

일괄 적용 대신 라이브 스택 보호 위해 그룹별 적용+pytest 검증.

현재

임베더 BGE-M3 검증+버전핀(1.4.0 dtype버그→1.2.11/transformers 4.44.2). embed_query를 asyncio.to_thread로 offload. 턴 파이프라인 배선: retrieve_persona_memory→episodic recall, search_kb→kb_behavior_cues. KB seed 3청크 적재. rag.py latent 버그 3건 수정. GraphRAG는 2차 보류.

성능 회귀 수정

최초 RAG 배선이 회기 시작/턴에 임베더 로드(~7-13s)를 동기 awaitPOST /sessions가 막힘 → session-layout E2E 4건 타임아웃. 수정: 회상·KB 회수를 _warm_rag_caches 백그라운드 task로 옮기고 턴은 캐시만 읽음(비차단). 이번 동의 게이트 E2E 재검증 중 여러 세션 warm task가 BGE-M3를 동시에 지연 로드해 API health/dev-login timeout이 반복된 문제는 embedder load/encode RLock과 warm semaphore 1개로 안정화했다. 재검증: session-layout 4/4 + 핵심 E2E 27/27·11/11·아바타 4/4, 최신 layout/session 조합 11/11 통과.

시각 게이트

admin 1280 input "text-clipped-x" 1건은 폼 input 값 스크롤(설계상 정상) 오탐 — form 컨트롤을 text-clip 판정에서 제외했다. 당시 게이트는 7/7, 현재 strict gate는 빈 회기리뷰 포함 9/9다.

심층 상세 · 검증 로그

로드맵 단계표, 단계별 상세 작업·검증 명령 표, 이번 세션 내러티브. 기본 접힘 — 필요할 때만 펼쳐서 본다.

전체 개발 로드맵 (5단계)MASTERPLAN + REVISIONS
Phase 0closing

기반 정렬 / 운영 안전장치

인프라, 인증 경계, fail-closed, 공개 배포, 회귀 테스트 안정화.

  • 상태: 대부분 완료, 공개 /turn proof만 남음
  • 검증: build + backend 178 기준선 + Playwright 113 기준선, Node/backend focused 120
  • 운영: prod API 8001, engine 9099, tunnel 1개
Phase 1next

MVP 코어 루프

1 페르소나 상담, 실제 턴 저장, 회기말 피드백, 저항 엔진 검증.

  • DoD: 로그인 → P1 상담 완주 → 피드백 수신
  • 추가: 음성 왕복 1턴 PoC 포함
  • 증거: cost/turn DB 적재 + openness 곡선 대조
Phase 2aplanned

3역할 / OAuth / 페르소나

학습자, 교수자, 관리자 권한 분리와 페르소나 확장.

  • DoD: 타인 session 직접 접근 403
  • 범위: OAuth BFF, RBAC, 교수 검수 루프
  • 주의: visible_to와 인간 권한은 별도 레이어
Phase 2bplanned

음성 캐스케이드

STT → 엔진 → TTS 왕복, WSS/터널 안정성, barge-in 로깅.

  • DoD: 50분 WSS 끊김 실측
  • 범위: Deepgram STT, OpenAI TTS, voice preset
  • 리스크: 실시간 레이턴시는 실측 전 완료 표기 금지
Phase 3planned

교육용 파일럿 / 효과성

수련생 파일럿, 사전사후 효과 측정, 재귀학습 데이터셋 산출.

  • DoD: 20명 완주 + KPI 충족
  • 측정: SUS, 자기효능감, κ/ICC, 환각률
  • 정정: IRB 게이트가 아니라 데이터 활용동의/개인정보 게이트
정정 기준: MASTERPLAN 원본의 IRB 신청/승인 게이트는 MASTERPLAN_REVISIONS 기준으로 제거한다. Phase 2는 2a(3역할/OAuth/페르소나)와 2b(음성 캐스케이드)로 분리하고, Phase 1 DoD에는 얇은 음성 왕복 1턴 PoC를 포함한다.
단계별 상세 작업 · 검증 명령 표P0~결정 / 검증 50+행

Phase 0 마감 상세

지금 단계는 구현을 더 벌리는 단계가 아니라, 운영 증거를 닫고 P1 진입 조건을 고정하는 단계다.

상태: 조건부우선순위: P0블로커: public /turn
BLOCK
공개 Google OAuth 실제 /turn proof 확보

로그인 가능한 계정으로 storageState를 저장한 뒤 public-auth-turn E2E를 실행한다.

산출물

apps/web/node_modules/.tmp/public-auth.json, public /turn 성공 로그

검증

E2E_PUBLIC_AUTH=1 + chromium-public-auth 1회 통과

DONE
engine config 운영값 재확인

GET /admin/engine-config로 운영값을 직접 확인했다. engine_mode=claude_cli, engine_url=http://127.0.0.1:9099, model=gateway-default, durable=true, source=database로 의도값과 일치한다.

산출물

config API 응답(durable, db source)

검증

claude_cli / http://127.0.0.1:9099 / gateway-default 의도값 대조 완료

DONE
과거 Live2D demo 자산 제거

Mao/Haru 샘플, Pixi/Cubism 런타임, 공개 /live2d/* 캐시 잔여 접근을 차단했다.

산출물

apps/web/functions/live2d/[[path]].js, P1 PSB raster avatar + SVG fallback

검증

운영 /live2d/mao/*, Cubism core 404

DONE
프로세스 난립 정리

운영 확인용 프로세스와 로컬/Tailnet 검증용 dev 프로세스를 의도적으로 분리해 유지한다.

산출물

9099 engine, 8001 prod API, 8000/8010 dev API, 5173 web, cloudflared tunnel 1개

검증

Get-NetTCPConnection에서 대상 포트별 listener 확인

GATE
한신대 공문/데이터 거버넌스 게이트 정리

SSO 클레임, 추가 축어록 수급, 미성년 원본 활용동의, 개인정보 처리방침을 P1 진입 전 외부 의존성으로 명확히 둔다. 로컬 문서 골격은 준비됐지만 한신대/데이터 steward의 written evidence는 아직 필요하다.

산출물

docs/ops/hanshin-data-governance-gate.md, 공문 질의 항목, 동의 범위 체크리스트, SSO claim mapping 표

검증

문서 artifact 작성 완료; IRB 게이트가 아니라 데이터/SSO 게이트로 외부 증거 필요 상태 유지

팀장 판정: 로컬 회귀와 공개 prod-safe 게이트는 통과했다. 2026-06-29 수동 Cloudflare Pages production 배포가 현재 custom domain asset으로 확인됐고, 운영 반영 전 남은 핵심 증거는 공개 Google OAuth 실제 /turn smoke다. 런타임 mock/demo 자산은 운영 번들에서 제거하고 테스트 fixture만 남긴다.

Phase 0 완료 상세 목록

이전 "완료" 카드 내용을 탭 안에서 항목별로 보존한다.

DONE
DB persona 기반 테스트

session-persistence, teacher, voice/api/personas에서 실제 DB persona를 가져온다.

산출물

seed persona 직접 결합 제거, 실제 persona catalog 기반 E2E

검증

persona 관련 E2E가 DB-backed persona로 통과

DONE
비개발 런타임 보호

prod/staging에서 dev login, seed fallback, OAuth 누락, 기본 secret, localhost CORS를 거부한다.

산출물

fail-closed runtime config validation

검증

backend runtime policy unittest 통과

DONE
compose OAuth 주입

ENVIRONMENT, Google OAuth vars, CORS_ORIGINS, OPENAI_BASE_URL을 API 컨테이너에 명시했다.

산출물

compose 환경 주입 정리

검증

docker compose -f infra\docker-compose.yml --env-file infra\.env.example config --quiet 더미 필수 env 세트로 통과

DONE
admin 모바일 overflow 수정

긴 readiness detail을 안정화했고, 사용자 변경은 DB 저장소가 durable할 때만 허용한다.

산출물

admin responsive layout, durable store gate, AI health copy

검증

admin manage-users desktop/mobile 2 passed

DONE
global engine config 테스트 분리

db-persistence는 전역 engine config를 바꾸므로 @single-run으로 분리했다.

산출물

Playwright single-run tagging

검증

전체 Playwright 113 passed

DONE
handoff와 문서 갱신

docs/HANDOFF.md, docs/dev_dashboard.html, apps/web/e2e/README.md를 최신 상태로 맞췄다.

산출물

handoff, dashboard, E2E README

검증

최신 커밋 기준 상태/검증/운영 포트 일치

DONE
공개 API prod 전환

cloudflared ingress를 127.0.0.1:8001 prod API로 돌렸고 public readiness gate가 통과했다.

산출물

prod API 8001, tunnel config

검증

https://api-vignette.chanpaca.net/health prod/db/engine OK

DONE
공개 런타임 재기동 스크립트

scripts/start-public-runtime.ps1scripts/stop-public-runtime.ps1로 API 8001과 cloudflared를 재현 가능하게 만들었다.

산출물

public runtime start/stop scripts

검증

engine 9099 + API 8001 + cloudflared only

DONE
persona catalog 인증화

/personas는 이제 비로그인 401이다. 로컬과 공개 API 모두 직접 확인했고 E2E 회귀를 추가했다.

산출물

authenticated persona catalog boundary

검증

local/public unauth GET /personas 401

DONE
public API base fallback 보강

public 웹에서 /api/auth/config가 SPA HTML로 떨어지지 않도록 public API origin fallback을 추가했다.

산출물

public API origin fallback

검증

public login screen E2E 1 passed

DONE
학습자 홈 0회 기록 상태

학습자 홈은 fake 기록 없이 실제 서버 세션 0개를 기존 회기 빈 상태와 0 카운트로 표시한다.

산출물

empty history UX, zero count, no fake stats

검증

learner screenshots: document overflow 0, empty history visible

DONE
로컬 5175 로그인 재기동 검증

127.0.0.1:5175 web, 8000 API, 9099 engine을 재기동하고 dev-login 서버 세션과 /learn redirect를 확인했다.

산출물

local dev-login flow proof

검증

desktop/mobile auth flow passed

DONE
active session dense viewport 보정

375px급 active session에서 stage 내부가 1px 넘게 잘리던 문제를 avatar/padding 조정으로 제거했다.

산출물

session layout responsive adjustment

검증

session-layout desktop/mobile 8 passed

DONE
텍스트 턴 SSE 전환

POST /sessions/{id}/stream token 이벤트로만 내담자 자막을 쌓고, HTTP 거절과 SSE error 모두 실패 턴을 transcript와 저장소에 남기지 않는다.

산출물

real SSE stream turn path, failed turn discard

검증

session layout/turn E2E passed

DONE
음성 실패 턴 미저장

음성 회기도 엔진 응답 생성 성공 후에만 learner/client turn을 저장한다. 실패한 transcript는 pending 상태에서 제거한다.

산출물

voice failure persistence guard

검증

voice + voice-success + session-layout 15 passed

DONE
비영구 상태 문구 정리

admin health와 voice degraded reason은 내부 fallback/seed 표현 대신 비영구 런타임 상태를 사용자용 문구로 표시한다.

산출물

operator-facing copy cleanup

검증

settings/admin focused E2E 통과

DONE
직접 URL persona 차단

/learn/session/:code로 직접 들어와도 DB 원본 persona가 아니면 회기 시작을 막는다.

산출물

route guard, degraded/seed fallback regression

검증

unavailable persona code E2E 통과

DONE
demo Live2D 자산 제거

Mao/Haru 샘플과 Pixi/Cubism 런타임을 공개 배포물에서 제거했다. 세션 화면은 P1 PSB raster avatar와 SVG fallback을 렌더링한다.

산출물

P1 PSB raster avatar, SVG fallback, legacy /live2d/* 404 function

검증

Mao/Haru/Cubism core routes 404, no Live2D/Pixi chunks

이번 세션 내러티브 (historical snapshot · 현재 숫자 아님)team lead review

이번 세션은 seed persona 결합 제거, 비개발 fail-closed 설정, compose OAuth 주입, admin 모바일 overflow 수정, persona 인증 경계 보강, 전체 Playwright 재통과, 공개 API prod 전환까지 완료했다. 이번 이어받기에서는 내담자 AI 생성 턴의 provider/model/token/cost 텔레메트리를 /turn, SSE stream, 음성 생성 경로에서 DB app.turns까지 적재하도록 연결했고, gateway stream done/error 계약을 구조화했으며, P1 상담 시작→텍스트 stream→종료→리뷰 피드백 UI E2E와 저항 엔진 정량 회귀 테스트를 추가했다. 이어서 PII 마스킹 게이트 우회 차단, RBAC/IDOR·감사 insert 경로, 음성 paralinguistic 메타 저장, 공개 런타임 watchdog 스크립트와 runbook을 보강했다. 추가로 상주 engine session reuse, SAML provider 스캐폴드, visible_to 런타임 필터, EOT/TTS payload 계약까지 회귀화했다. 백엔드 회귀 테스트는 84개로 재검증했고, 페르소나 승인/반려 API와 교수 콘솔 검수 UI proof를 추가했다. Postgres RLS/audit live smoke checker와 s2s 2차 PoC 판단 메모도 준비했지만, live DB 실행과 s2s 채택 결정은 별도 증거가 필요하다. 이후 로컬 5175 런타임 재기동, admin 사용자 관리 E2E 안정화, 학습자 홈 0회 기록 UX, active session 실제 SSE 스트림 전환, 텍스트/음성 실패 턴 미저장, 직접 URL persona 우회 차단, Live2D demo 자산 제거, AI 운영 fail-closed 보강까지 전체 Playwright로 확인했다. 2026-06-26 15:18 KST 기준 공개 API 터널을 복구한 뒤 전체 Playwright 72개가 desktop/mobile/single-run 모두 통과했다. Cloudflare Pages production 배포 2a58ca49도 완료했고, 커스텀 도메인이 새 asset을 서빙하는 것을 확인했다. 과거 Mao/Haru Live2D demo 자산은 repo와 번들에서 제거했고, /live2d/* 운영 경로는 404로 차단했다. 현재 런타임은 prod API 8001, dev API 8000/8010, web 5173, engine gateway 9099, cloudflared tunnel 1개를 의도적으로 유지한다. 이번 라운드에서는 레이아웃 작업을 서브에이전트 전담으로 분리해 세션, 학습자 홈/리뷰, 관리자/교수자, 설정 화면을 overflow 수리 수준이 아니라 실제 작업 화면 재설계 수준으로 갱신했다. 통합 레이아웃 E2E는 desktop/mobile 54개를 최종 통과했고, 자세한 인수인계는 docs/ops/layout-redesign-handoff-2026-06-26.md에 남겼다. 이번 이어받기에서는 핸드오프가 남긴 "실기기 시각 수용"을 엄격한 자동 게이트로 닫았다. 7개 권장 너비(390/720/861/900/1024/1280/1440)에서 초기 7개 재설계 화면의 가로 overflow 0·컨트롤 클립 0을 강제하는 layout-visual-gate.spec.ts(초기 7 passed/49장, 현재 빈 회기리뷰 포함 9 passed/63장)를 만들고, 화면별 1에이전트 병렬 시각 리뷰 → 6개 병렬 수정 에이전트 → 화면별 적대적 재검수까지 돌려 7/7 accept(critical/major 0, 회귀 0)를 확정했다. 학습자홈 1280 3열 가운데 컬럼 붕괴(critical)와 교수 'API 404' raw 배너를 포함한 결함을 모두 해소했고, 레이아웃 항목을 DONE으로 닫았다. 이번 로그인 복구 라운드에서는 dev/Tailnet OAuth direct start까지 local_oauth_unavailable로 막고, public OAuth state를 HttpOnly 쿠키에 묶인 서명 토큰으로 바꿔 API 재시작 후 콜백이 invalid_state로 실패하지 않게 했으며, callback 실패 reason/status 진단 로그와 화면 메시지 분기를 추가했다. 추가로 Google provider error를 access_denied/provider_error로 분리하고 로그인 화면에 reason code를 노출했다. 이어서 C1 사례개념화 워크시트는 리뷰 API·편집 UI·app.case_worksheet 저장/재조회 경로까지 2차로 연결했다. 공개 Google OAuth 실제 /turn 증명만 storageState 캡처 후 남아 있다.