- 02-learner-home-responsive.png
+ 02-learner-home-responsive-v2-dark-unified.pngdiff --git a/docs/design-concepts/generated/02-learner-home-responsive-v2-dark-unified.png b/docs/design-concepts/generated/02-learner-home-responsive-v2-dark-unified.png new file mode 100644 index 0000000..d0a60c8 Binary files /dev/null and b/docs/design-concepts/generated/02-learner-home-responsive-v2-dark-unified.png differ diff --git a/docs/design-concepts/generated/03-session-responsive-v2-dark-unified.png b/docs/design-concepts/generated/03-session-responsive-v2-dark-unified.png new file mode 100644 index 0000000..e802dc7 Binary files /dev/null and b/docs/design-concepts/generated/03-session-responsive-v2-dark-unified.png differ diff --git a/docs/design-concepts/generated/04-session-review-responsive-v2-dark-unified.png b/docs/design-concepts/generated/04-session-review-responsive-v2-dark-unified.png new file mode 100644 index 0000000..29ff302 Binary files /dev/null and b/docs/design-concepts/generated/04-session-review-responsive-v2-dark-unified.png differ diff --git a/docs/design-concepts/prompts/vignette-dark-ui-refresh-2026-06-28.md b/docs/design-concepts/prompts/vignette-dark-ui-refresh-2026-06-28.md new file mode 100644 index 0000000..fbba8e2 --- /dev/null +++ b/docs/design-concepts/prompts/vignette-dark-ui-refresh-2026-06-28.md @@ -0,0 +1,102 @@ +# Vignette dark UI refresh prompts — 2026-06-28 + +목적: 기존 `03-session-responsive.png`가 다크 스테이지와 라이트 패널을 섞어 보여서, 현재 구현된 dark active UI와 맞는 디자인 컨셉 이미지를 다시 생성했다. 추가로 회기 리뷰와 학습자 홈도 같은 제품 언어로 보정했다. + +## 근거 + +- 실제 구현 기준: `apps/web/src/pages/Session.tsx`, `apps/web/src/pages/session/session.css`, `apps/web/src/styles/tokens.css` +- 최신 검증 캡처: `docs/design-verification/full-pages/03-session-active-desktop.png`, `03-session-active-tablet.png`, `03-session-active-mobile.png` +- 디자인 SSOT: `docs/DESIGN_CONCEPT.md`, `docs/dev_dashboard.html` +- UX 원칙: 상태 가시성, progressive disclosure, 한 화면 한 의도, 대비 기준 + - Nielsen Norman Group, 10 Usability Heuristics: https://www.nngroup.com/articles/ten-usability-heuristics/ + - Nielsen Norman Group, Progressive Disclosure: https://www.nngroup.com/articles/progressive-disclosure/ + - Material Design dark theme guidance: https://m2.material.io/design/color/dark-theme.html + - WCAG contrast guidance: https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html + +## 산출물 + +- `docs/design-concepts/generated/03-session-responsive-v2-dark-unified.png` +- `docs/design-concepts/generated/04-session-review-responsive-v2-dark-unified.png` +- `docs/design-concepts/generated/02-learner-home-responsive-v2-dark-unified.png` + +## 공통 프롬프트 규칙 + +- 운영 기본은 dark 화면이다. active UI에 off-white, beige, light card를 섞지 않는다. +- 기본 팔레트: app `#0E1714`, panel `#17231F`, input `#22302C`, stage `#0E1614`, hairline `#2A363C`, accent `#6FB3A4`, clay `#CC8F77`. +- 진짜 기능명만 쓴다. 장식용 hero, marketing copy, 가짜 과장 차트는 금지한다. +- 학습자는 지금 해야 할 한 가지 행동을 즉시 알아야 한다. +- 실시간 피드백은 중앙 과업을 가로막지 않는 주변 신호로 둔다. +- 주요 한글 라벨은 읽히게 하고, 작은 본문은 UI 질감 수준이면 된다. + +## 03-session-responsive v2 prompt + +```text +Use case: ui-mockup +Asset type: responsive product UI concept sheet for Vignette live counseling session, 16:9 wide raster image. +Primary request: Create a polished product UI concept sheet showing the live counseling session screen in desktop, tablet, and mobile mockups on one wide canvas. This is not a landing page. It must feel like a real immersive clinical training application currently in use. + +Reference behavior from the current app: live counseling session has a left client-context column, a phase tracker, a central dark client stage with a semi-realistic client avatar bust and a breathing voice orb, a real-time transcript, a text fallback composer, a live-signal rail, client observation meters, safety status, and a bottom control bar with mic state, pause, screen mode segmented control, and session end. + +Required layout: +- Desktop mockup: three-region layout. Left narrow rail contains “내담자 컨텍스트” and “회기 진행”. Center has dark client stage on top, “실시간 자막” transcript below, and “학습자 발화를 입력하세요.” composer. Right rail has “라이브 신호”, “관찰 신호”, and “안전 점검”. Bottom control bar shows “마이크 꺼짐”, segmented control “몰입 / 상태 신호 / 코칭”, “일시정지”, and “회기 종료”. +- Tablet mockup: top compact context strip, stage and transcript stacked, controls at bottom. +- Mobile mockup: compact vertical flow with context chips, avatar stage, transcript, input, and thumb-friendly controls. + +Visual style: +- Unified dark theme only. Use #0E1714 app background, #17231F panels, #22302C inputs, #0E1614 stage, #2A363C hairlines. +- Sage teal accent #6FB3A4 for active state and focus, terracotta #CC8F77 only for learner/client voice warmth, muted amber only for safety attention. +- No off-white or beige panels anywhere in the active session UI. No light theme mixed into the image. +- Quiet clinical training room, not a sci-fi dashboard. Subtle paper/noise texture may exist inside dark surfaces only. +- Typography feels like Pretendard: Korean UI, strong weight hierarchy, compact labels, tabular timer. +- Corners are modest: 6-12 px, not bubbly. Hairline borders, very soft shadows. + +UX principles to embody: +- One screen, one intent: listening and responding to the client. +- Visibility of system status: phase, mic state, live signal, safety state are readable at a glance. +- Progressive disclosure: real-time feedback is quiet and peripheral, not intrusive. +- Strong contrast for important text; no low-contrast tiny labels for primary controls. + +Text hints: “라이브 상담 세션”, “내담자 컨텍스트”, “회기 진행”, “라포”, “실시간 자막”, “라이브 신호”, “관찰 신호”, “안전 점검”, “학습자 발화를 입력하세요.”, “마이크 꺼짐”, “상태 신호”, “코칭”, “회기 종료”. + +Avoid: mixed light/dark theme, white cards, beige side panels, bright dashboards, neon glow, sci-fi HUD, giant decorative hero, stock photo background, marketing copy, emoji, red-heavy warnings, score fireworks, radar charts, nested cards, cartoon icons, watermark, browser chrome. +``` + +## 04-session-review v2 prompt + +```text +Use case: ui-mockup +Asset type: responsive product UI concept sheet for Vignette session review, 16:9 wide raster image. +Primary request: Create a polished product UI concept sheet showing the completed session review screen in desktop, tablet, and mobile mockups on one wide canvas. This is a reflective clinical education workbench, not a score dashboard and not a marketing page. + +Reference behavior from the current app and source: session review includes session headline metadata, audio replay, PDF export, emotion balance timeline, phase flow, annotated transcript, technique labels, supervisor comments, good moments, growth points, next utterance suggestion, and case conceptualization worksheet. + +Visual style: +- Unified dark theme only. Use #0E1714 app background, #17231F panels, #22302C nested surfaces, #2A363C dividers, #6FB3A4 sage teal accents. +- Use muted slate-blue for AI/supervisor notes, muted amber only for watch items, terracotta sparingly for client-side warmth. +- No off-white or beige panels anywhere. No bright white review cards. +- Calm, dense but readable clinical training record. + +Text hints: “회기 리뷰”, “감정 밸런스 타임라인”, “회기 흐름”, “세션 트랜스크립트”, “좋았던 순간”, “다음 회기 개선점”, “슈퍼바이저 코멘트”, “오디오 다시 듣기”, “PDF 내보내기”, “사례개념화 워크시트”, “근거 보기”, “다음 발화 제안”. + +Avoid: mixed light/dark theme, white cards, beige paper background, score fireworks, gamified badges, radar charts, dense red warnings, decorative hero illustration, stock-photo desk, emoji, cartoon icons, browser chrome, watermark. +``` + +## 02-learner-home v2 prompt + +```text +Use case: ui-mockup +Asset type: responsive product UI concept sheet for Vignette learner home / practice dashboard, 16:9 wide raster image. +Primary request: Create a polished product UI concept sheet showing the learner home screen in desktop, tablet, and mobile mockups on one wide canvas. This is the first working screen after login: the learner chooses or continues a counseling practice session. It must be a real app workbench, not a landing page. + +Reference behavior from the current app and source: learner home has persona selection, selected client preview, presenting problem, difficulty, counseling approach, voice/session info, start new session or continue action, active/review/total session stats, recent sessions, review-ready items, and AI coach recommendations. + +Visual style: +- Unified dark theme only. Use #0E1714 app background, #17231F panels, #22302C cards/inputs, #2A363C hairlines, #6FB3A4 sage teal CTA and active states. +- Terracotta #CC8F77 only for client warmth or small persona initial. Amber only for “권장 액션”/review wait state. +- No light theme surfaces, no white cards, no beige sections. +- Dense but calm SaaS/education workbench. Prioritize scanning, comparison, and repeated action. + +Text hints: “오늘의 회기를 준비합니다.”, “연습 대상”, “내담자 목록”, “서연(가명)”, “고2”, “우울/자살사고”, “주호소”, “난도”, “접근”, “음성”, “새 회기 시작”, “이어하기”, “기존 회기”, “리뷰 대기”, “다음 연습 추천”, “최근 기록”, “AI 코치”, “권장 액션”. + +Avoid: mixed light/dark theme, bright white panels, beige cards, marketing hero, oversized illustration, empty decorative graph, gamified leaderboard, emoji, cartoon mascots, red-heavy alerts, browser chrome, watermark. +``` diff --git a/docs/dev_dashboard.html b/docs/dev_dashboard.html index 86c98ab..b5ad9c6 100644 --- a/docs/dev_dashboard.html +++ b/docs/dev_dashboard.html @@ -499,6 +499,16 @@
64차 적용(2026-06-28): refactor-governance 1차로 C1 생성 워크시트 section/item spec을 CASE_WORKSHEET_SECTION_SPECS와 case_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을 제거했다. Login은 pages/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.reachedPhase와 LiveCoachEvent.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.py의 StageLabel/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_status에 worksheet_status/worksheet_note/worksheet_reviewed_at을 붙이고, PUT /teacher/sessions/{id}/review-status가 승인·수정요청·반려 결정을 저장한다. /sessions/{id}/review의 teacherReview는 교수자/관리자에게만 이 결정을 노출하고, 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/sync와 scripts/sync-persona-sources.py는 refresh=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): 다크 UI v2 시안을 실제 코드 적용 라운드로 승격했다. 기준 이미지는 03-session-responsive-v2-dark-unified.png, 04-session-review-responsive-v2-dark-unified.png, 02-learner-home-responsive-v2-dark-unified.png이고, 실행 순서는 라이브 상담 세션 → 회기 리뷰 → 학습자 홈이다. 생성 PNG는 앱에 직접 넣지 않고 React/CSS 구현의 톤·밀도·반응형 기준으로만 사용한다. 추적 항목은 docs/ops/backlog-2026-06-26.md, 구현 체크리스트는 docs/ops/dark-ui-refresh-implementation-plan-2026-06-28.md에 둔다. DONE 표기는 실제 Playwright 캡처와 typecheck/build/focused E2E/layout gate 통과 뒤에만 한다.
76차 적용(2026-06-28): refactor-governance P2로 M2 digest 계약 경계를 고정했다. SessionDigestInput/SessionDigestResult/SessionSummaryWrite를 추가해 future LLM worker가 raw text, evaluator-only turn, 평가 payload, CCD, deterministic carry를 우회하지 못하게 했다. /sessions/{id}/end의 digest_pending은 여전히 CompressionJob 생성 여부를 알리는 비동기 압축 필요 신호일 뿐이며, LLM digest worker 실행·압축 품질 검증 완료를 뜻하지 않는다. 검증: M2 focused 74 passed, npm run check:api-types, npm run typecheck, py_compile.
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의 역할·코호트·승인 상태를 이어받는다. 미등록 외부 도메인 로그인은 계속 차단한다.
01-login-responsive.png
- 02-learner-home-responsive.png
+ 02-learner-home-responsive-v2-dark-unified.png
@@ -554,12 +564,12 @@
12-learner-dashboard-redesign-concept-b-alt.png
- 03-session-responsive.png
+ 03-session-responsive-v2-dark-unified.png
- 04-session-review-responsive.png
+ 04-session-review-responsive-v2-dark-unified.png
@@ -647,8 +657,8 @@
doc1/4/5가 1급 산출물로 요구하는 워크시트 축(탐색 11항목·호소 5영역·인지삼제·1/2차 감정·보호/방해 4사분면·생물심리사회 목표)을 SessionReviewResponse.caseWorksheet로 노출한다. 서버는 저장된 축어록만 근거로 규칙 기반 초안을 만들고, 프론트 리뷰 화면은 근거 턴으로 점프 가능한 워크시트를 편집할 수 있다. 저장본은 PUT /sessions/{id}/review/worksheet → app.case_worksheet에 저장되고, 이후 GET /review는 saved_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을 참조한다.
pytest app/test_session_turn_persistence.py app/test_rbac_idor.py -q 25 passed, py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_case_worksheet_rubric.py -q 4 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 -m py_compile apps/api/app/services/case_worksheet_rubric.py apps/api/app/test_case_worksheet_rubric.py scripts/check-case-worksheet-rubric.py, npm run check:api-types, npm run typecheck, npm run build passed. 로컬 API smoke: 세션 생성→워크시트 저장→GET /review가 saved_by_learner:local smoke saved worksheet 반환.
임상팀 확정 루브릭 콘텐츠, level anchor, 가중치, 컷오프, 예시 답안, AI 채점 적용/calibration, 교수자 승인·반려·수정요청 검수 플로우는 후속이다. 현재 scaffold는 형식 검증과 로드 경로만 보장하며, 초안/저장본은 숨은 정답키나 임상 점수 없이 학습자 편집 산출물로만 취급한다.
doc1/4/5가 1급 산출물로 요구하는 워크시트 축(탐색 11항목·호소 5영역·인지삼제·1/2차 감정·보호/방해 4사분면·생물심리사회 목표)을 SessionReviewResponse.caseWorksheet로 노출한다. 서버는 저장된 축어록만 근거로 규칙 기반 초안을 만들고, 프론트 리뷰 화면은 근거 턴으로 점프 가능한 워크시트를 편집할 수 있다. 저장본은 PUT /sessions/{id}/review/worksheet → app.case_worksheet에 저장되고, 이후 GET /review는 saved_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 /review가 saved_by_learner:local smoke saved worksheet 반환.
임상팀 확정 루브릭 콘텐츠, level anchor, 가중치, 컷오프, 예시 답안, AI 채점 적용/calibration, 승인 후 잠금·재제출 정책, 재귀학습 데이터셋 approved 연계는 후속이다. 현재 scaffold는 형식 검증과 로드 경로, 교수자 수동 판정 저장만 보장하며, 초안/저장본은 숨은 정답키나 임상 점수 없이 학습자 편집 산출물로만 취급한다.
텍스트 /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로 새 문서를 만든다. 학습자 SessionReview 데스크톱은 좌측 축어록 타임라인과 우측 요약·감정·흐름·루브릭·강점·개선점·pre/post·워크시트·피드백 작업열의 2열 구조로 재배치했고, 교수자 검토 화면과 모바일/태블릿은 기존 반응형 규칙을 유지한다.
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 python -B -m pytest -p no:cacheprovider app/test_live_coach_sources.py -q 4 passed, py -3.11 scripts\sync-persona-sources.py --help, 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. 이번 2열 UI 검증: npm run typecheck, 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, session-review__1440-desktop.png 직접 확인. 로컬 DB smoke: source pack sync 4 sources/15 chunks embedded, 재호출 skipped_unchanged=4, official_suicide_risk_guidelines source filter 검색 성공.
임상팀 골든셋·라이브 코칭 루브릭 검수, 공식 지침/논문 source pack 추가와 임상 검수 상태 운영정책 보강이 남아 있다.
텍스트 /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 데스크톱은 좌측 축어록 타임라인과 우측 요약·감정·흐름·루브릭·강점·개선점·pre/post·워크시트·피드백 작업열의 2열 구조로 재배치했고, 교수자 검토 화면과 모바일/태블릿은 기존 반응형 규칙을 유지한다.
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. 이번 2열 UI 검증: npm run typecheck, 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, session-review__1440-desktop.png 직접 확인. 로컬 DB smoke: source pack sync 4 sources/15 chunks embedded, 재호출 skipped_unchanged=4, official_suicide_risk_guidelines source filter 검색 성공.
임상팀 골든셋·라이브 코칭 루브릭 검수, 공식 지침/논문 source pack 추가와 임상 검수 상태 운영정책 보강이 남아 있다.
정규식 폴백에 한국어 고위험 패턴 추가: 구체 날짜(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 15개로 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.json과 data/privacy/pii-masking-eval-report.schema.json은 Node.js 전환 후에도 공유 가능한 입력/리포트 계약이며, 기본 리포트는 evidence_text_included=false로 masked_text/forbidden_remaining 원문 증거를 제외한다. fixture가 잡은 소속/안내/이름 NAME 오탐은 stopword로 보정했고, 66차에서는 자연 발화형 이름 라벨·자기소개 케이스를 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. Presidio가 설치돼도 한국어 누락을 막기 위해 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 43 passed, py -3.11 -X utf8 scripts/evaluate-pii-masking.py --json 15/15 pass, 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 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은 15-case summary-only harness까지 완료했지만, Presidio ko/spaCy ko/의료·학교 도메인 NER 기반 정밀화, 실제 운영 말뭉치 기반 오탐/미탐 평가, 미성년/guardian 및 법무 검토가 필요한 최종 서명 동의서·개인정보 처리방침·약관 evidence는 외부/후속 게이트다. 공개 Google OAuth 실제 /turn proof는 별도 운영 게이트.
정규식 폴백에 한국어 고위험 패턴 추가: 구체 날짜(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 15개로 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.json과 data/privacy/pii-masking-eval-report.schema.json은 Node.js 전환 후에도 공유 가능한 입력/리포트 계약이며, 기본 리포트는 evidence_text_included=false로 masked_text/forbidden_remaining 원문 증거를 제외한다. fixture가 잡은 소속/안내/이름 NAME 오탐은 stopword로 보정했고, 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 15/15 pass, 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은 15-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 색인한다. 시작/턴/voice 경로는 다음 회기 seed recall에서 case_digest, 직전 session_summary, client-visible non-contradicted pinned_fact, episodic recall 단편을 조립해 사용한다. 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 승격 기준·LLM digest 압축, M3 운영 SAML 서명검증·기관 claim schema/test tenant·deprovisioning audit는 후속이다.
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 인자 경계를 명시했다. future LLM digest worker 입력은 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 승격 기준·LLM digest worker 실행/품질평가/재압축, M3 운영 SAML 서명검증·기관 claim schema/test tenant·deprovisioning audit는 후속이다.
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-only 8차, M3 claim 1차, L1 schema-backed engine gateway golden fixture·Node.js conformance runner·session/persona read-model 분리는 완료. 운영 SAML 서명검증은 기관 IdP 의존이다.digest_pending 응답 계약까지이며, LLM digest worker 실행·품질평가·재압축은 완료로 보지 않는다. H3 원문 격리는 hash-only artifact와 RAG index 차단까지이며, 암호화 blob/vault 원문 실저장은 아직 후속이다. 실제 provider 기반 한숨·울음 감지, H4 legal/guardian evidence, M3 운영 IdP, L1 외부 거버넌스는 아직 게이트다.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.py와 apps/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, SessionReviewResponse, worksheet aggregate, TeacherDashboardResponse, TeacherSessionSummary, TeacherLearnerGrowth에 이어 SessionStartResponse, SessionDetailResponse, SessionDetailTurn도 ApiSchema<...> alias로 전환했다. 세션 응답 stage는 backend StageLabel enum으로 고정해 OpenAPI가 라포|탐색|개입|정리 union을 생성한다. generated optional/default 차이는 화면 form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. .github/workflows/api-contract.yml은 PR/master push에서 같은 stale check를 실행해 OpenAPI type drift를 CI에서 막는다.
npm run check:api-types, npm run typecheck, npm run build passed. python -m pytest app/test_session_turn_persistence.py -q 16 passed. 영향 범위 E2E: learner.spec.ts + session-review.spec.ts + teacher.spec.ts + layout-visual-gate.spec.ts 32 passed.
평가 조회·리뷰 도식·교수 콘솔 등 아직 자유 문자열로 남긴 stage-like 필드는 각 도메인 의미를 확인한 뒤 별도 축소한다.
apps/api/scripts/export-openapi.py와 apps/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, SessionDetailTurn도 ApiSchema<...> alias로 전환했다. 세션 응답 stage는 backend StageLabel enum으로 고정해 OpenAPI가 라포|탐색|개입|정리 union을 생성한다. 69차에서는 리뷰 도식 ReviewPhaseSegment.key를 rapport|explore|intervene|closing, ReviewPhaseSegment.label과 SessionReviewResponse.reachedPhase를 StageLabel로 좁혔다. 평가 조회 EvaluationSummary.stage는 평가 미생성 시 null, 평가 트리거 응답 stage와 교수 콘솔 TeacherSessionSummary/TeacherGrowthPoint stage는 StageLabel union으로 생성된다. 71차에서는 공개 공유 응답 PublicSessionShareResponse.reachedPhase와 live-coach 이력 LiveCoachEvent.stage도 StageLabel | null 계약으로 축소했다. 72차에서는 app/stage_contract.py가 StageLabel, 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로 고정했다.
gateway SSE token/done/error와 generate cost 계약은 회귀화했다. 이번 패스에서 app/contracts/engine_gateway.py를 추가해 FastAPI engine_client와 현재 Python gateway가 같은 GenerateRequest/GenerateResponse/Stream*Event 계약을 import한다. 이어서 EngineGatewaySseLineDecoder로 token/done/error decode와 validation도 같은 계약 모듈에 모았다. 최신 패스에서는 raw SSE line 해석을 EngineClient.stream_packets() 경계로 올리고, Python gateway /v1/generate가 GenerateResponse.model_dump()로 응답하게 해 hand-mirrored dict drift를 줄였다. Node.js gateway로 갈아치울 때 보존해야 할 첫 wire contract다. claude_cli와 Messages API live parity는 ANTHROPIC_API_KEY 의존 B2 항목으로 분리한다.
app/contracts/engine_gateway.py, app/engine_client.py, app/services/orchestrator.py, engine_gateway/gateway.py, engine_gateway/test_gateway_model.py, engine_gateway/golden/*, scripts/check-engine-gateway-contract.mjs shared contract + stream packet tests
node scripts/check-engine-gateway-contract.mjs --json, python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py -q 19 passed, python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q 50 passed.
claude -p 엔진풀 실증session_id가 살아 있는 gateway session을 재사용하고 missing session은 ephemeral로 닫히는 구조를 회귀화했고, live 게이트웨이(9099)로 실측까지 마쳤다. probe-engine-gateway.py가 단일 session_id로 2회 reused stream을 돌려 TTFT 1667–4199ms, cost 누적 0.068→0.123(turns 1→2), engine=claude_p, model=opus-4-8을 측정했다.
engine_gateway/test_gateway_model.py, scripts/probe-engine-gateway.py, live probe JSON
session reuse 7 tests OK; live probe 2 reused streams, TTFT/cost 실측, session_id 재사용 확인
current turn, recall, pinned fact, recent turns가 Presidio/가명처리 후에만 engine request로 들어가게 막았다. 한국어 이름/기관 로컬 휴리스틱 1차와 15-case summary-only 합성 fixture 평가 harness/input-report schema를 추가했고, fast evaluator prompt와 client turn text_masked도 마스킹본을 쓰게 보강했다. 66차에서는 자연 발화형 이름 라벨·자기소개 케이스를 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 외부 LLM 호출은 audit.llm_call_log에 provider/model/token/cost/inference_geo/latency metadata만 남기고 prompt/completion 본문은 저장하지 않는다.
app/services/pii_masking_eval.py, data/privacy/pii-masking-ko-fixtures.json, data/privacy/pii-masking-eval-input.schema.json, data/privacy/pii-masking-eval-report.schema.json, scripts/evaluate-pii-masking.py, app/test_pii_masking_eval.py, app/test_orchestrator_masking.py, app/test_evaluation_persistence.py, app/test_session_turn_persistence.py, session_persistence.record_llm_call_audit
phone/email/RRN 및 한국어 NAME/ORG raw 값이 generate/stream/evaluator payload에 없음, 감사 payload 본문 미포함, fixture 15/15 pass, focused 회귀 43 passed, 현재 전체 API 178 기준 유지, 로컬 live /turn smoke 후 audit.llm_call_log 8→11
current turn, recall, pinned fact, recent turns가 Presidio/가명처리 후에만 engine request로 들어가게 막았다. 한국어 이름/기관 로컬 휴리스틱 1차와 15-case summary-only 합성 fixture 평가 harness/input-report schema를 추가했고, fast evaluator prompt와 client turn text_masked도 마스킹본을 쓰게 보강했다. 66차에서는 자연 발화형 이름 라벨·자기소개 케이스를 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 70차에서는 선택형 ko recognizer adapter를 mask_pii() 경계에 추가하고, adapter 성공/실패 모두 regex fallback이 마지막 안전망으로 남는지 회귀화했다. 외부 LLM 호출은 audit.llm_call_log에 provider/model/token/cost/inference_geo/latency metadata만 남기고 prompt/completion 본문은 저장하지 않는다.
app/services/guardrail.py, app/services/pii_masking_eval.py, data/privacy/pii-masking-ko-fixtures.json, data/privacy/pii-masking-eval-input.schema.json, data/privacy/pii-masking-eval-report.schema.json, scripts/evaluate-pii-masking.py, app/test_pii_masking_eval.py, app/test_orchestrator_masking.py, app/test_evaluation_persistence.py, app/test_session_turn_persistence.py, session_persistence.record_llm_call_audit
phone/email/RRN 및 한국어 NAME/ORG raw 값이 generate/stream/evaluator payload에 없음, adapter fake span과 fallback phone 마스킹 경로 검증, adapter 실패 시 regex fallback 유지, 감사 payload 본문 미포함, fixture 15/15 pass, focused 회귀 47 passed, 현재 전체 API 178 기준 유지, 로컬 live /turn smoke 후 audit.llm_call_log 8→11
로컬 DB-backed 런타임에서 P1 선택, SSE 상담, 종료, 회기말 리뷰 피드백을 한 번에 통과시켰다. 실제 운영 로그인 proof는 P0 public OAuth blocker와 분리해 남긴다.
e2e/session-mvp.spec.ts, turn telemetry 저장 경로
npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1 1 passed.
공감 발화와 조언점프 발화 5턴 세트를 상태머신 회귀 테스트와 실제 API+DB smoke로 비교했다. live smoke는 dev-login→onboarding→P1 세션 2개→각 5턴을 수행하고 app.session_state, app.turns, app.turn_client_state를 직접 조회했다. 공감 세션은 3턴부터 탐색으로 열리고 openness가 0.171까지 상승했다. 조언점프 세션은 라포와 openness 0.0을 유지했다.
app/test_state_machine_resistance.py, scripts/smoke-resistance-openness-db.py, docs/ops/resistance-openness-db-smoke-2026-06-28.json
pytest app/test_state_machine_resistance.py app/test_evaluation_persistence.py -q 9 passed; live DB smoke PASS.
Session UI 마이크 버튼 경로에서 synthetic getUserMedia/MediaRecorder로 실제 /voice/ws를 열고 audio_start, binary chunk, audio_end, transcript, reply, TTS 재생 상태까지 검증했다. 추가로 로컬 live API에서 실제 STT/TTS provider를 통과해 TTS 바이너리를 수신했다. 물리 마이크, 공개 WSS, 50분 양방향 실측은 별도 운영 smoke로 남아 있다.
apps/web/e2e/voice-success.spec.ts, controlled OpenAI/engine providers, Web Audio browser voice UI probe, live /voice/ws smoke
npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run 2 passed; live P2 WS smoke binary 66 chunks/266,496 bytes
npm run typecheckPLAYWRIGHT_PORT=5174 npm run e2e@single-run DB 영속화·음성 성공 경로까지 전체 통과.npm run check:api-typessrc/lib/api.gen.ts stale checknpm run typecheck / npm run build / session persistence / learner + session-review + teacher + layout gateApiSchema aliases with UI fallback. Stage responses are OpenAPI enum unions. Focused E2E 32 passed. .github/workflows/api-contract.yml now runs npm run check:api-types on API/Web contract changes.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 / npm run check:api-types / npm run typecheck / npm run buildApiSchema aliases with UI fallback. Stage responses are OpenAPI enum unions, including review phase key/label, reached phase, evaluation summary/trigger responses, and teacher session/growth stage. .github/workflows/api-contract.yml runs npm run check:api-types on API/Web contract changes.pytest app/test_session_share.py app/test_session_turn_persistence.py -q / npm run generate:api-types / npm run typecheckrobots.txt/sitemap.xml/llms.txt added.python -m pytest app/ -qpython -B -m pytest -p no:cacheprovider app/test_usage_report.py app/test_evaluator_model_routing.py app/test_runtime_policy.py app/test_admin_ops.py -q / scripts/report-ai-usage.py sample CLI / prior web gatespython -B -m pytest -p no:cacheprovider app/test_persona_review.py app/test_live_coach_sources.py -qrag.index_document() rejects sensitivity=3/raw-marker chunks before DB access.npm run typecheck / npm run build / npm run check:api-types / npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1 / npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1 / npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1npm run typecheck / npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1 / npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1python -B -m pytest -p no:cacheprovider app/test_session_memory.py app/test_session_turn_persistence.py app/test_orchestrator_masking.py app/test_runtime_policy.py -qapp.turn_embedding idempotently.py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_session_memory.py app/test_session_turn_persistence.py app/test_orchestrator_masking.py app/test_runtime_policy.py -q / npm run check:api-types / npm run typecheckapp.turn_embedding idempotently, next-session route-level turn proof shows DB seed recall/pinned facts entering EngineMessage L2/L4 with raw names masked, and SessionDigestInput/SessionDigestResult/SessionSummaryWrite keep digest worker input/output and session_summary write shape contract-only. SessionEndResponse.digest_pending remains a boolean compression-needed signal in OpenAPI/generated types.python -B -m py_compile app/session_read_model.py app/routes/sessions.py / python -B -m pytest -p no:cacheprovider app/test_session_turn_persistence.py app/test_evaluation_persistence.py app/test_session_share.py app/test_learner_dashboard.py app/test_rbac_idor.py app/test_teacher_dashboard.py -q / npm run check:api-types / npm run typecheckapp/session_read_model.py now owns session list/dashboard/detail/review/share DTOs and deterministic builders while routes/sessions.py keeps auth, RLS-backed reads, persistence, and route decorators.python -B -m py_compile app/persona_read_model.py app/routes/personas.py / python -B -m pytest -p no:cacheprovider app/test_persona_review.py -q / python -B -m pytest -p no:cacheprovider app/test_persona_review.py app/test_session_turn_persistence.py -q / npm run check:api-types / npm run typecheckapp/persona_read_model.py now owns persona catalog/review/draft/source/evidence DTOs and deterministic mappers while routes/personas.py keeps auth, teacher/admin gates, DB repository calls, RAG source registration, LLM draft generation, and HTTP error mapping.python -B -m pytest -p no:cacheprovider app/test_voice_ws.py app/test_session_turn_persistence.py app/test_voice_service.py -qevent_type/category taxonomy into app.turns.provider_events. 인증된 review nonverbal은 정규화 taxonomy 일부만 파생 칩으로 노출하며 raw transcript/text/provider/source/raw type은 응답하지 않는다.py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_case_worksheet_rubric.py app/test_session_turn_persistence.py app/test_rbac_idor.py -q / scripts/check-case-worksheet-rubric.py --json / npm run check:api-types / npm run typecheck / npm run build / local API smokecaseWorksheet, learner-edited saved_by_learner worksheet persists through app.case_worksheet, and case-worksheet-rubric@scaffold-2026-06-28 validates 5 sections/28 items with scoring_enabled=false. Local smoke returned saved_by_learner:local smoke saved worksheet. Stage labels are normalized at response boundaries.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 / scripts/check-case-worksheet-rubric.py --json / npm run check:api-types / npm run typecheck / npm run build / session-review.spec.ts / local API smokecaseWorksheet, learner-edited saved_by_learner worksheet persists through app.case_worksheet, teacher/admin teacherReview exposes manual worksheetStatus/worksheetNote/worksheetReviewedAt, and case-worksheet-rubric@scaffold-2026-06-28 validates 5 sections/28 items with scoring_enabled=false. Local smoke returned saved_by_learner:local smoke saved worksheet. Stage labels are normalized at response boundaries.python -B -m pytest -p no:cacheprovider app/test_auth_providers.py -qpython -m pytest app/test_session_turn_persistence.py app/test_voice_ws.py -qpython -m pytest app/test_rbac_idor.py app/test_teacher_dashboard.py -q / npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1/teach/session/:id/review read-only with learner worksheet editing blocked for teachers.pytest app/test_learner_dashboard.py app/test_teacher_dashboard.py app/test_rbac_idor.py -q / npm run check:api-types / npm run typecheck / npm run buildGET /sessions/dashboard uses shared session_metrics and feeds LearnerHome overview/growth/persona_progress/recent_feedback/last-session recap. Focused learner/review/teacher/layout/session E2E evidence recorded in backlog.pytest app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_learner_dashboard.py -q / npm run typecheck / npm run build / teacher.spec.tsapp.session_review_status, teacher dashboard review_status/review_note/reviewed_at, and PUT /teacher/sessions/{session_id}/review-status are documented. 검토 큐 visible 회귀와 교수자 전용 review grid 영역도 테스트로 고정했다.pytest app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_learner_dashboard.py -q / npm run typecheck / npm run build / teacher.spec.tsapp.session_review_status, teacher dashboard review_status/review_note/reviewed_at, worksheet worksheet_status/worksheet_note/worksheet_reviewed_at, and PUT /teacher/sessions/{session_id}/review-status are documented. 검토 큐 visible 회귀와 교수자 전용 review grid 영역도 테스트로 고정했다.python -B -m pytest -p no:cacheprovider app/test_persona_review.py -qpython -B -m pytest -p no:cacheprovider app/test_live_coach_sources.py -q / py -3.11 scripts\sync-persona-sources.py --helppy -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_live_coach_sources.py -q / py -3.11 -X utf8 scripts\sync-persona-sources.py --helpapp.test_session_turn_persistence.SessionTurnPersistenceTestnode scripts/check-engine-gateway-contract.mjs --json / python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py -q / python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py -qEngineClient.stream_packets() owns raw SSE decode, EngineGatewaySseLineDecoder parses token/done/error from the shared contract, keeps legacy token text compatibility, treats provider [DONE] as an ignored compatibility line rather than completion, rejects unknown gateway events, validates schema-backed engine_gateway_contract.v1.json, and gateway error event does not create saved turns.engine_gateway.test_gateway_modelGenerateResponse response validation, SSE frames/decoder, live session_id reuse and ephemeral close fixedapp/test_pii_masking_eval.py app/test_orchestrator_masking.py app/test_evaluation_persistence.py app/test_session_turn_persistence.pytext_masked에 남지 않음. Synthetic ko fixture 15/15 pass, 자연 발화형 이름 라벨·자기소개와 negative control 포함, input/report schema validation, summary-only report(evidence_text_included=false), entity recall 1.0, forbidden substring removal 1.0, unexpected entity violations 0.app/test_pii_masking_eval.py app/test_orchestrator_masking.py app/test_evaluation_persistence.py app/test_session_turn_persistence.pytext_masked에 남지 않음. Optional ko recognizer fake span은 [NAME]/[ORG]로 마스킹되고 같은 문장의 phone은 후단 regex가 처리하며, adapter 실패 시에도 regex fallback이 유지된다. Synthetic ko fixture 15/15 pass, 자연 발화형 이름 라벨·자기소개와 negative control 포함, input/report schema validation, summary-only report(evidence_text_included=false), entity recall 1.0, forbidden substring removal 1.0, unexpected entity violations 0.app.test_rbac_idorpython -m pytest app/test_auth_providers.py app/test_admin_ops.py -q / npm run typecheck / npm run check:api-typesadmin_access and super_admin are exposed through auth/admin DTOs. Super admins can grant/revoke admin page access; configured super admins stay protected. Frontend role switching uses canAccessRole.python -m pytest app/test_admin_ops.py app/test_runtime_policy.py -q / authenticated local public-API smoke/admin/usage returns 200 with source=database, durable=true. Cloudflare blocked raw Python public smoke with 1010, so app-level HTTP was verified against 127.0.0.1:8001 using the same prod process.