다크 UI 시안과 운영 문서 갱신
This commit is contained in:
parent
30441bd93b
commit
d9458d0e40
8 changed files with 269 additions and 41 deletions
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
|
|
@ -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.
|
||||||
|
```
|
||||||
|
|
@ -499,6 +499,16 @@
|
||||||
<p class="dg-note">64차 적용(2026-06-28): refactor-governance 1차로 C1 생성 워크시트 section/item spec을 <code>CASE_WORKSHEET_SECTION_SPECS</code>와 <code>case_worksheet_template_item_keys()</code>로 올려 루브릭 validator/CLI/test가 더미 <code>ReviewTurn</code> 없이 같은 템플릿 키를 참조하게 했다. API/DB/frontend 응답 shape와 저장본 우선순위는 바꾸지 않았다. 검증: C1 focused 34 passed, rubric CLI PASS, py_compile, residual grep.</p>
|
<p class="dg-note">64차 적용(2026-06-28): refactor-governance 1차로 C1 생성 워크시트 section/item spec을 <code>CASE_WORKSHEET_SECTION_SPECS</code>와 <code>case_worksheet_template_item_keys()</code>로 올려 루브릭 validator/CLI/test가 더미 <code>ReviewTurn</code> 없이 같은 템플릿 키를 참조하게 했다. API/DB/frontend 응답 shape와 저장본 우선순위는 바꾸지 않았다. 검증: C1 focused 34 passed, rubric CLI PASS, py_compile, residual grep.</p>
|
||||||
<p class="dg-note">65차 적용(2026-06-28): refactor-governance 2차로 프론트 inline CSS template literal을 제거했다. <code>Login</code>은 <code>pages/login/LoginBrand.tsx</code>, <code>LoginPanel.tsx</code>, <code>login.css</code>로 분리해 인증 상태 로직과 프레젠테이션을 나눴고, <code>PendingApproval</code>, <code>Onboarding</code>, <code>LearnerHome</code>, <code>Professor</code>, <code>PersonaStudio</code>, <code>ClientAvatar</code>의 <code>*_CSS</code> 상수는 인접 CSS 파일 import로 옮겼다. 검증: inline <code><style>{..._CSS}</style></code> residual 0건, <code>npm run typecheck</code>, <code>npm run build</code>, 로그인 Playwright 렌더 스모크.</p>
|
<p class="dg-note">65차 적용(2026-06-28): refactor-governance 2차로 프론트 inline CSS template literal을 제거했다. <code>Login</code>은 <code>pages/login/LoginBrand.tsx</code>, <code>LoginPanel.tsx</code>, <code>login.css</code>로 분리해 인증 상태 로직과 프레젠테이션을 나눴고, <code>PendingApproval</code>, <code>Onboarding</code>, <code>LearnerHome</code>, <code>Professor</code>, <code>PersonaStudio</code>, <code>ClientAvatar</code>의 <code>*_CSS</code> 상수는 인접 CSS 파일 import로 옮겼다. 검증: inline <code><style>{..._CSS}</style></code> residual 0건, <code>npm run typecheck</code>, <code>npm run build</code>, 로그인 Playwright 렌더 스모크.</p>
|
||||||
<p class="dg-note">66차 적용(2026-06-28): H4 자연 발화형 한국어 이름 마스킹 보강으로 <code>제 이름은 김서연입니다</code>, <code>보호자 이름은 박민수입니다</code>, <code>저는 최하늘입니다</code> 패턴과 <code>이름은 중요하지 않다</code> negative control을 합성 fixture에 추가했다. <code>이름</code> stopword와 자기소개형 이름 패턴을 generic 성씨+조사 패턴보다 앞에 두어 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 검증: H4 focused 43 passed, CLI fixture 15/15 pass, py_compile.</p>
|
<p class="dg-note">66차 적용(2026-06-28): H4 자연 발화형 한국어 이름 마스킹 보강으로 <code>제 이름은 김서연입니다</code>, <code>보호자 이름은 박민수입니다</code>, <code>저는 최하늘입니다</code> 패턴과 <code>이름은 중요하지 않다</code> negative control을 합성 fixture에 추가했다. <code>이름</code> stopword와 자기소개형 이름 패턴을 generic 성씨+조사 패턴보다 앞에 두어 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 검증: H4 focused 43 passed, CLI fixture 15/15 pass, py_compile.</p>
|
||||||
|
<p class="dg-note">67차 적용(2026-06-28): M2 다음 회기 회상 주입 증거를 route-level로 고정했다. REST submit과 SSE stream이 공통 <code>_prepare_turn_context()</code> 경계에서 seed recall, pinned fact, 최근 client-visible 턴, KB cue를 조립하게 했고, 새 테스트가 DB <code>case_digest</code>·직전 <code>session_summary</code>·client-visible <code>pinned_fact</code>가 다음 턴 EngineMessage의 L2/L4 블록에 들어가며 raw 이름은 <code>[NAME]</code>으로 마스킹되는지 검증한다. 검증: M2 focused 68 passed, py_compile.</p>
|
||||||
|
<p class="dg-note">68차 적용(2026-06-28): refactor-governance P1 delete-only로 <code>routes/sessions.py</code>의 미사용 <code>_load_prev_case_summary()</code>와 죽은 <code>_LEARNER_VISIBLE_AI_ROLE</code> 상수를 제거했다. 동작 변경 없이 다음 M2 작업 표면을 줄인 정리다. 검증: residual grep 0건, M2 focused 68 passed, py_compile.</p>
|
||||||
|
<p class="dg-note">69차 적용(2026-06-28): refactor-governance P3 계약 축소로 리뷰 도식 <code>ReviewPhaseSegment.key/label</code>, <code>SessionReviewResponse.reachedPhase</code>, 평가 조회 <code>EvaluationSummary.stage</code>, 평가 트리거 응답 stage, 교수 콘솔 <code>TeacherSessionSummary</code>/<code>TeacherGrowthPoint</code> stage를 OpenAPI union으로 좁혔다. 공유 카드 <code>reachedPhase</code>와 live-coach event stage는 저장 payload/legacy 호환 검토가 필요해 별도 후속으로 남겼다. 검증: backend focused 50 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>.</p>
|
||||||
|
<p class="dg-note">70차 적용(2026-06-28): H4 마스킹 경계를 refactor-governance 기준으로 좁혀 <code>guardrail.mask_pii()</code> 내부에 선택형 한국어 PII recognizer adapter를 추가했다. adapter는 import-time hard dependency가 아니며 실패 시 예외를 삼키고 기존 regex fallback을 마지막 안전망으로 유지한다. fake adapter 회귀는 regex가 못 잡는 별명/기관 span을 <code>[NAME]</code>/<code>[ORG]</code>로 마스킹하고 같은 문장의 전화번호는 후단 regex가 <code>[PHONE]</code>으로 처리하는지, adapter 실패 시에도 regex fallback이 유지되는지 검증한다. span 중복 처리는 문자별 set 할당 대신 정렬된 <code>last_end</code> 비교로 정리했다. 검증: H4 focused 47 passed, CLI fixture 15/15 pass, py_compile.</p>
|
||||||
|
<p class="dg-note">71차 적용(2026-06-28): refactor-governance P3 계약 축소 후속으로 공유 카드 <code>PublicSessionShareResponse.reachedPhase</code>와 <code>LiveCoachEvent.stage</code>도 OpenAPI enum/null 계약으로 좁혔다. 저장된 legacy JSON/TEXT 값은 <code>stage_label_or_none()</code> validator에서 한글 라벨 또는 기존 code key만 살리고, 빈 문자열·임의 문자열은 <code>null</code>로 낮춰 기존 공유 링크·live-coach 이력이 500으로 깨지지 않게 했다. 세션 UI 코칭 이력은 null stage를 <code>단계 미상</code>으로 표시한다. 검증: share/live-coach focused 43 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>.</p>
|
||||||
|
<p class="dg-note">72차 적용(2026-06-28): refactor-governance P3 계약 SSOT 후속으로 stage 라벨과 리뷰 phase key 변환 규칙을 <code>app/stage_contract.py</code>로 분리했다. <code>turn_runtime.py</code>의 legacy code 정규화, <code>session_read_model.py</code>의 <code>StageLabel</code>/<code>ReviewPhaseKey</code>, <code>routes/eval.py</code>의 nullable summary stage membership 체크가 같은 모듈을 참조한다. OpenAPI 응답 shape는 유지하고, enum/legacy/unknown 처리만 한 곳으로 모았다. 검증: stage contract focused 42 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>.</p>
|
||||||
|
<p class="dg-note">73차 적용(2026-06-28): C1 워크시트 교수자 수동 검수 골격을 추가했다. 기존 <code>app.session_review_status</code>에 <code>worksheet_status</code>/<code>worksheet_note</code>/<code>worksheet_reviewed_at</code>을 붙이고, <code>PUT /teacher/sessions/{id}/review-status</code>가 승인·수정요청·반려 결정을 저장한다. <code>/sessions/{id}/review</code>의 <code>teacherReview</code>는 교수자/관리자에게만 이 결정을 노출하고, <code>SessionReview</code> 교수자 카드에는 학습자 저장 워크시트가 있을 때만 수동 검수 버튼을 활성화한다. AI 채점·루브릭 점수·자동 승인·재제출 잠금 정책은 만들지 않았다. 검증: C1 focused 40 passed, API types/typecheck/build, session-review E2E 3 passed.</p>
|
||||||
|
<p class="dg-note">74차 적용(2026-06-28): refactor-governance P4로 H2 live-coach source pack 캐시 경계를 명시했다. <code>live_coach</code>의 로컬 source pack snapshot은 API 프로세스 생명주기 캐시로 유지하고, 관리자 <code>POST /kb/live-coach/source-packs/sync</code>와 <code>scripts/sync-persona-sources.py</code>는 <code>refresh=True</code>로 캐시를 비운 뒤 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.</p>
|
||||||
|
<p class="dg-note">75차 계획(2026-06-28): 다크 UI v2 시안을 실제 코드 적용 라운드로 승격했다. 기준 이미지는 <code>03-session-responsive-v2-dark-unified.png</code>, <code>04-session-review-responsive-v2-dark-unified.png</code>, <code>02-learner-home-responsive-v2-dark-unified.png</code>이고, 실행 순서는 라이브 상담 세션 → 회기 리뷰 → 학습자 홈이다. 생성 PNG는 앱에 직접 넣지 않고 React/CSS 구현의 톤·밀도·반응형 기준으로만 사용한다. 추적 항목은 <code>docs/ops/backlog-2026-06-26.md</code>, 구현 체크리스트는 <code>docs/ops/dark-ui-refresh-implementation-plan-2026-06-28.md</code>에 둔다. DONE 표기는 실제 Playwright 캡처와 typecheck/build/focused E2E/layout gate 통과 뒤에만 한다.</p>
|
||||||
|
<p class="dg-note">76차 적용(2026-06-28): refactor-governance P2로 M2 digest 계약 경계를 고정했다. <code>SessionDigestInput</code>/<code>SessionDigestResult</code>/<code>SessionSummaryWrite</code>를 추가해 future LLM worker가 raw text, evaluator-only turn, 평가 payload, CCD, deterministic carry를 우회하지 못하게 했다. <code>/sessions/{id}/end</code>의 <code>digest_pending</code>은 여전히 CompressionJob 생성 여부를 알리는 비동기 압축 필요 신호일 뿐이며, LLM digest worker 실행·압축 품질 검증 완료를 뜻하지 않는다. 검증: M2 focused 74 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, py_compile.</p>
|
||||||
<p class="dg-note">20차 적용(2026-06-28): 신규 Google/SAML 사용자는 <code>account_status=pending</code>으로 시작하고 승인 전에는 <code>/pending</code> 안내 화면만 본다. <code>yunchan@twentyoz.kr</code>는 슈퍼 관리자 allowlist로 admin+approved를 받으며, <code>/admin/users</code>는 가입 승인 탭에서 pending 계정을 승인 또는 보류 처리한다.</p>
|
<p class="dg-note">20차 적용(2026-06-28): 신규 Google/SAML 사용자는 <code>account_status=pending</code>으로 시작하고 승인 전에는 <code>/pending</code> 안내 화면만 본다. <code>yunchan@twentyoz.kr</code>는 슈퍼 관리자 allowlist로 admin+approved를 받으며, <code>/admin/users</code>는 가입 승인 탭에서 pending 계정을 승인 또는 보류 처리한다.</p>
|
||||||
<p class="dg-note">21차 적용(2026-06-28): 관리자 페이지 진입권을 기본 역할과 분리해 <code>app_user.admin_access</code>로 저장한다. <code>AUTH_SUPER_ADMIN_EMAILS</code> 기본값은 <code>yunchan@twentyoz.kr</code>, <code>hoonjungkoo@hs.ac.kr</code>이며, 슈퍼 관리자는 학습자·교수자·관리자 공간 전환과 관리자 권한 부여/회수를 할 수 있다. 학생·교수 계정도 <code>admin_access=true</code>면 우측 상단 관리자 진입이 노출된다. 구성 슈퍼 관리자의 권한 회수와 계정 비활성화는 차단한다.</p>
|
<p class="dg-note">21차 적용(2026-06-28): 관리자 페이지 진입권을 기본 역할과 분리해 <code>app_user.admin_access</code>로 저장한다. <code>AUTH_SUPER_ADMIN_EMAILS</code> 기본값은 <code>yunchan@twentyoz.kr</code>, <code>hoonjungkoo@hs.ac.kr</code>이며, 슈퍼 관리자는 학습자·교수자·관리자 공간 전환과 관리자 권한 부여/회수를 할 수 있다. 학생·교수 계정도 <code>admin_access=true</code>면 우측 상단 관리자 진입이 노출된다. 구성 슈퍼 관리자의 권한 회수와 계정 비활성화는 차단한다.</p>
|
||||||
<p class="dg-note">25차 적용(2026-06-28): <code>/admin/users</code>에서 허용 도메인 밖 이메일도 정확한 계정 단위로 강제 등록할 수 있다. Google/SAML/dev-login은 미리 등록된 이메일만 도메인 게이트 예외로 통과시키고, provider 로그인 시 기존 관리 row의 역할·코호트·승인 상태를 이어받는다. 미등록 외부 도메인 로그인은 계속 차단한다.</p>
|
<p class="dg-note">25차 적용(2026-06-28): <code>/admin/users</code>에서 허용 도메인 밖 이메일도 정확한 계정 단위로 강제 등록할 수 있다. Google/SAML/dev-login은 미리 등록된 이메일만 도메인 게이트 예외로 통과시키고, provider 로그인 시 기존 관리 row의 역할·코호트·승인 상태를 이어받는다. 미등록 외부 도메인 로그인은 계속 차단한다.</p>
|
||||||
|
|
@ -542,8 +552,8 @@
|
||||||
<div class="dg-card-body"><b>로그인/진입</b><span>OAuth, 허용 도메인, 로컬 역할 선택, 교육용 고지를 신뢰 중심으로 정리한 진입 화면.</span><code>01-login-responsive.png</code></div>
|
<div class="dg-card-body"><b>로그인/진입</b><span>OAuth, 허용 도메인, 로컬 역할 선택, 교육용 고지를 신뢰 중심으로 정리한 진입 화면.</span><code>01-login-responsive.png</code></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="dg-card">
|
<article class="dg-card">
|
||||||
<img src="design-concepts/generated/02-learner-home-responsive.png" alt="학습자 홈 PC 태블릿 모바일 반응형 디자인 시안" loading="lazy">
|
<img src="design-concepts/generated/02-learner-home-responsive-v2-dark-unified.png" alt="학습자 홈 PC 태블릿 모바일 다크 테마 디자인 시안" loading="lazy">
|
||||||
<div class="dg-card-body"><b>학습자 홈</b><span>내담자 선택, 회기 시작, 이전 회기 이어가기, 리뷰 가능 상태를 작업대처럼 배치.</span><code>02-learner-home-responsive.png</code></div>
|
<div class="dg-card-body"><b>학습자 홈</b><span>내담자 선택, 회기 시작, 이전 회기 이어가기, 리뷰 가능 상태를 다크 작업대 톤으로 배치.</span><code>02-learner-home-responsive-v2-dark-unified.png</code></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="dg-card">
|
<article class="dg-card">
|
||||||
<img src="design-concepts/generated/11-learner-dashboard-redesign-concept-b.png" alt="학습자 대시보드 회기 리캡 중심 재설계 시안" loading="lazy">
|
<img src="design-concepts/generated/11-learner-dashboard-redesign-concept-b.png" alt="학습자 대시보드 회기 리캡 중심 재설계 시안" loading="lazy">
|
||||||
|
|
@ -554,12 +564,12 @@
|
||||||
<div class="dg-card-body"><b>학습자 대시보드 대안</b><span>마지막 내담자 반응과 내 마지막 반응을 더 강하게 드러낸 대안 레이아웃.</span><code>12-learner-dashboard-redesign-concept-b-alt.png</code></div>
|
<div class="dg-card-body"><b>학습자 대시보드 대안</b><span>마지막 내담자 반응과 내 마지막 반응을 더 강하게 드러낸 대안 레이아웃.</span><code>12-learner-dashboard-redesign-concept-b-alt.png</code></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="dg-card">
|
<article class="dg-card">
|
||||||
<img src="design-concepts/generated/03-session-responsive.png" alt="라이브 상담 세션 PC 태블릿 모바일 반응형 디자인 시안" loading="lazy">
|
<img src="design-concepts/generated/03-session-responsive-v2-dark-unified.png" alt="라이브 상담 세션 PC 태블릿 모바일 다크 테마 디자인 시안" loading="lazy">
|
||||||
<div class="dg-card-body"><b>라이브 상담 세션</b><span>내담자 무대, 자막, 입력, 라이브 신호, 안전 상태를 몰입형 훈련 화면으로 구성.</span><code>03-session-responsive.png</code></div>
|
<div class="dg-card-body"><b>라이브 상담 세션</b><span>내담자 무대, 자막, 입력, 라이브 신호, 안전 상태를 다크 몰입형 훈련 화면으로 구성.</span><code>03-session-responsive-v2-dark-unified.png</code></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="dg-card">
|
<article class="dg-card">
|
||||||
<img src="design-concepts/generated/04-session-review-responsive.png" alt="회기 리뷰 PC 태블릿 모바일 반응형 디자인 시안" loading="lazy">
|
<img src="design-concepts/generated/04-session-review-responsive-v2-dark-unified.png" alt="회기 리뷰 PC 태블릿 모바일 다크 테마 디자인 시안" loading="lazy">
|
||||||
<div class="dg-card-body"><b>회기 리뷰</b><span>감정 흐름, 축어록, 기법 태그, 교수자 콜아웃을 근거 기반 회고 화면으로 묶음.</span><code>04-session-review-responsive.png</code></div>
|
<div class="dg-card-body"><b>회기 리뷰</b><span>감정 흐름, 축어록, 기법 태그, 교수자 콜아웃을 다크 근거 기반 회고 화면으로 묶음.</span><code>04-session-review-responsive-v2-dark-unified.png</code></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="dg-card">
|
<article class="dg-card">
|
||||||
<img src="design-concepts/generated/05-professor-responsive.png" alt="교수 콘솔 PC 태블릿 모바일 반응형 디자인 시안" loading="lazy">
|
<img src="design-concepts/generated/05-professor-responsive.png" alt="교수 콘솔 PC 태블릿 모바일 반응형 디자인 시안" loading="lazy">
|
||||||
|
|
@ -647,8 +657,8 @@
|
||||||
<div class="tg-head"><h3>원천문서 갭 분석</h3><span class="tg-note">산학협력 원천문서 5종 정독 + 코드 교차조사 (2026-06-26). 근거: source-docs-gap-analysis-2026-06-26.md · critical 3 / high 4 / medium+ 6</span></div>
|
<div class="tg-head"><h3>원천문서 갭 분석</h3><span class="tg-note">산학협력 원천문서 5종 정독 + 코드 교차조사 (2026-06-26). 근거: source-docs-gap-analysis-2026-06-26.md · critical 3 / high 4 / medium+ 6</span></div>
|
||||||
<div class="scards">
|
<div class="scards">
|
||||||
<article class="scard" data-status="done" data-cat="콘텐츠·산출물" data-owner="0">
|
<article class="scard" data-status="done" data-cat="콘텐츠·산출물" data-owner="0">
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">C1 3차 DONE</span><span class="scard-mid"><span class="scard-title">사례개념화·치료계획 워크시트 저장형 구조<span class="critbadge">critical</span></span><span class="scard-sum">학습자 워크시트 저장 경로와 임상팀 루브릭 외부화 scaffold를 연결했다. 실제 루브릭 콘텐츠는 외부 의존.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-done">C1 4차 DONE</span><span class="scard-mid"><span class="scard-title">사례개념화·치료계획 워크시트 저장형 구조<span class="critbadge">critical</span></span><span class="scard-sum">학습자 저장, 임상팀 루브릭 외부화 scaffold, 교수자 수동 검수 상태 저장을 연결했다. 실제 루브릭 콘텐츠는 외부 의존.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
<div class="scard-body"><div class="kv"><b>현재</b><p>doc1/4/5가 1급 산출물로 요구하는 워크시트 축(탐색 11항목·호소 5영역·인지삼제·1/2차 감정·보호/방해 4사분면·생물심리사회 목표)을 <code>SessionReviewResponse.caseWorksheet</code>로 노출한다. 서버는 저장된 축어록만 근거로 규칙 기반 초안을 만들고, 프론트 리뷰 화면은 근거 턴으로 점프 가능한 워크시트를 편집할 수 있다. 저장본은 <code>PUT /sessions/{id}/review/worksheet</code> → <code>app.case_worksheet</code>에 저장되고, 이후 <code>GET /review</code>는 <code>saved_by_learner</code> 워크시트를 자동 초안보다 우선 반환한다. 3차에서는 임상팀 확정 루브릭을 코드 수정 없이 받을 수 있도록 <code>data/rubrics/case-worksheet-rubric.json</code>, <code>case-worksheet-rubric.schema.json</code>, <code>app.services.case_worksheet_rubric</code>, <code>scripts/check-case-worksheet-rubric.py</code>를 추가했다. 현재 rubric은 <code>status=scaffold_only</code>, <code>scoring_enabled=false</code>이고, 5개 section/28개 worksheet item key가 서버 생성 워크시트와 일치하는지만 검증한다. 64차에서는 템플릿 key source를 <code>CASE_WORKSHEET_SECTION_SPECS</code>/<code>case_worksheet_template_item_keys()</code>로 명시해 validator/CLI/test가 같은 생성 워크시트 spec을 참조한다.</p></div><div class="kv"><b>검증</b><p><code>pytest app/test_session_turn_persistence.py app/test_rbac_idor.py -q</code> 25 passed, <code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_case_worksheet_rubric.py -q</code> 4 passed, <code>py -3.11 -X utf8 scripts/check-case-worksheet-rubric.py --json</code> PASS(<code>items_total=28</code>, <code>scoring_enabled=false</code>, sha256 provenance), <code>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</code>, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code> passed. 로컬 API smoke: 세션 생성→워크시트 저장→<code>GET /review</code>가 <code>saved_by_learner:local smoke saved worksheet</code> 반환.</p></div><div class="kv k-warn"><b>잔여</b><p>임상팀 확정 루브릭 콘텐츠, level anchor, 가중치, 컷오프, 예시 답안, AI 채점 적용/calibration, 교수자 승인·반려·수정요청 검수 플로우는 후속이다. 현재 scaffold는 형식 검증과 로드 경로만 보장하며, 초안/저장본은 숨은 정답키나 임상 점수 없이 학습자 편집 산출물로만 취급한다.</p></div></div>
|
<div class="scard-body"><div class="kv"><b>현재</b><p>doc1/4/5가 1급 산출물로 요구하는 워크시트 축(탐색 11항목·호소 5영역·인지삼제·1/2차 감정·보호/방해 4사분면·생물심리사회 목표)을 <code>SessionReviewResponse.caseWorksheet</code>로 노출한다. 서버는 저장된 축어록만 근거로 규칙 기반 초안을 만들고, 프론트 리뷰 화면은 근거 턴으로 점프 가능한 워크시트를 편집할 수 있다. 저장본은 <code>PUT /sessions/{id}/review/worksheet</code> → <code>app.case_worksheet</code>에 저장되고, 이후 <code>GET /review</code>는 <code>saved_by_learner</code> 워크시트를 자동 초안보다 우선 반환한다. 3차에서는 임상팀 확정 루브릭을 코드 수정 없이 받을 수 있도록 <code>data/rubrics/case-worksheet-rubric.json</code>, <code>case-worksheet-rubric.schema.json</code>, <code>app.services.case_worksheet_rubric</code>, <code>scripts/check-case-worksheet-rubric.py</code>를 추가했다. 현재 rubric은 <code>status=scaffold_only</code>, <code>scoring_enabled=false</code>이고, 5개 section/28개 worksheet item key가 서버 생성 워크시트와 일치하는지만 검증한다. 64차에서는 템플릿 key source를 <code>CASE_WORKSHEET_SECTION_SPECS</code>/<code>case_worksheet_template_item_keys()</code>로 명시해 validator/CLI/test가 같은 생성 워크시트 spec을 참조한다. 73차에서는 <code>app.session_review_status</code>에 워크시트 수동 검수 상태·메모·시각을 붙여 교수자/관리자가 승인·수정요청·반려 결정을 남길 수 있게 했다.</p></div><div class="kv"><b>검증</b><p><code>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</code> 40 passed, <code>py -3.11 -X utf8 scripts/check-case-worksheet-rubric.py --json</code> PASS(<code>items_total=28</code>, <code>scoring_enabled=false</code>, sha256 provenance), <code>py -3.11 -X utf8 -m py_compile</code> focused files, <code>npm run generate:api-types</code>, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>, <code>session-review.spec.ts</code> 3 passed. 로컬 API smoke: 세션 생성→워크시트 저장→<code>GET /review</code>가 <code>saved_by_learner:local smoke saved worksheet</code> 반환.</p></div><div class="kv k-warn"><b>잔여</b><p>임상팀 확정 루브릭 콘텐츠, level anchor, 가중치, 컷오프, 예시 답안, AI 채점 적용/calibration, 승인 후 잠금·재제출 정책, 재귀학습 데이터셋 approved 연계는 후속이다. 현재 scaffold는 형식 검증과 로드 경로, 교수자 수동 판정 저장만 보장하며, 초안/저장본은 숨은 정답키나 임상 점수 없이 학습자 편집 산출물로만 취급한다.</p></div></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="scard" data-status="done" data-cat="안전·위기" data-owner="0">
|
<article class="scard" data-status="done" data-cat="안전·위기" data-owner="0">
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">C2 구조 DONE</span><span class="scard-mid"><span class="scard-title">위기 에스컬레이션·safety_events 배선</span><span class="scard-sum">crisis.risk_level→ideation_observed 전달 + escalate 시 safety_events 적재. 라이브 검증.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-done">C2 구조 DONE</span><span class="scard-mid"><span class="scard-title">위기 에스컬레이션·safety_events 배선</span><span class="scard-sum">crisis.risk_level→ideation_observed 전달 + escalate 시 safety_events 적재. 라이브 검증.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
|
|
@ -664,7 +674,7 @@
|
||||||
</article>
|
</article>
|
||||||
<article class="scard" data-status="done" data-cat="교수자·평가" data-owner="0">
|
<article class="scard" data-status="done" data-cat="교수자·평가" data-owner="0">
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">H2 LIVE DONE</span><span class="scard-mid"><span class="scard-title">턴별 fast-loop + 라이브 코칭·이력</span><span class="scard-sum">평가 정규화에 더해 워크북·DSM·공식 지침/RAG 기반 코칭 아바타, 근거 모달, 발화별 이력 오버레이와 source pack RAG sync를 연결.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-done">H2 LIVE DONE</span><span class="scard-mid"><span class="scard-title">턴별 fast-loop + 라이브 코칭·이력</span><span class="scard-sum">평가 정규화에 더해 워크북·DSM·공식 지침/RAG 기반 코칭 아바타, 근거 모달, 발화별 이력 오버레이와 source pack RAG sync를 연결.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
<div class="scard-body"><div class="kv"><b>현재</b><p>텍스트 <code>/turn</code>·<code>/stream</code>·음성 경로에서 fast-loop 턴 평가를 <code>app.feedback_scores</code>, <code>app.turn_technique</code>, <code>app.turn_client_state</code>, <code>app.supervisor_comment</code>, <code>app.alternative_utterance</code>로 정규화 적재 후 리뷰 조회에서 hydrate한다. 이번 패스에서 <code>app/services/live_coach.py</code>, <code>data/kb/live_coaching_workbook_0615.json</code>, <code>data/kb/live_coaching_sources/*.json</code>, <code>POST /sessions/{id}/live-coach</code>, <code>GET /sessions/{id}/live-coach</code>, <code>POST /kb/live-coach/source-packs/sync</code>, <code>app.live_coach_events</code>를 추가했다. 세션 UI는 코칭 모드에서 AI 코치 아바타 말풍선으로 즉시 개입하고, "근거 보기" 모달과 학습자 발화 우측 <code>C</code> 마커를 통해 받은 코칭 이력을 채팅 위 스크롤 오버레이로 보여준다. 저장 payload는 PII 마스킹 excerpt + 코칭 구조화 JSON이며, 공식 자료는 허가된 source pack의 version/citation/summary로 넣고 장문 원문·공식 문항을 재현하지 않는다. 같은 source pack은 <code>app.services.source_pack_sync</code>를 통해 evaluator 전용 RAG에 증분 색인하며, hash 변경 시 최신 active document version+1로 새 문서를 만든다. 학습자 <code>SessionReview</code> 데스크톱은 좌측 축어록 타임라인과 우측 요약·감정·흐름·루브릭·강점·개선점·pre/post·워크시트·피드백 작업열의 2열 구조로 재배치했고, 교수자 검토 화면과 모바일/태블릿은 기존 반응형 규칙을 유지한다.</p></div><div class="kv"><b>검증</b><p><code>python -m compileall app -q</code>, 과거 <code>pytest app/test_live_coach_sources.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q</code> 27 passed, source sync focused <code>python -B -m pytest -p no:cacheprovider app/test_live_coach_sources.py -q</code> 4 passed, <code>py -3.11 scripts\sync-persona-sources.py --help</code>, <code>npm run generate:api-types</code>, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>, <code>npm run e2e -- e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts</code> 15 passed. 이번 2열 UI 검증: <code>npm run typecheck</code>, <code>npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1</code> 3 passed, <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code> 9 passed, <code>session-review__1440-desktop.png</code> 직접 확인. 로컬 DB smoke: source pack sync 4 sources/15 chunks embedded, 재호출 <code>skipped_unchanged=4</code>, <code>official_suicide_risk_guidelines</code> source filter 검색 성공.</p></div><div class="kv k-warn"><b>잔여</b><p>임상팀 골든셋·라이브 코칭 루브릭 검수, 공식 지침/논문 source pack 추가와 임상 검수 상태 운영정책 보강이 남아 있다.</p></div></div>
|
<div class="scard-body"><div class="kv"><b>현재</b><p>텍스트 <code>/turn</code>·<code>/stream</code>·음성 경로에서 fast-loop 턴 평가를 <code>app.feedback_scores</code>, <code>app.turn_technique</code>, <code>app.turn_client_state</code>, <code>app.supervisor_comment</code>, <code>app.alternative_utterance</code>로 정규화 적재 후 리뷰 조회에서 hydrate한다. 이번 패스에서 <code>app/services/live_coach.py</code>, <code>data/kb/live_coaching_workbook_0615.json</code>, <code>data/kb/live_coaching_sources/*.json</code>, <code>POST /sessions/{id}/live-coach</code>, <code>GET /sessions/{id}/live-coach</code>, <code>POST /kb/live-coach/source-packs/sync</code>, <code>app.live_coach_events</code>를 추가했다. 세션 UI는 코칭 모드에서 AI 코치 아바타 말풍선으로 즉시 개입하고, "근거 보기" 모달과 학습자 발화 우측 <code>C</code> 마커를 통해 받은 코칭 이력을 채팅 위 스크롤 오버레이로 보여준다. 저장 payload는 PII 마스킹 excerpt + 코칭 구조화 JSON이며, 공식 자료는 허가된 source pack의 version/citation/summary로 넣고 장문 원문·공식 문항을 재현하지 않는다. 같은 source pack은 <code>app.services.source_pack_sync</code>를 통해 evaluator 전용 RAG에 증분 색인하며, hash 변경 시 최신 active document version+1로 새 문서를 만든다. 74차에서는 로컬 source pack 캐시의 key/lifetime/무효화 경계를 명시해 live turn은 API 프로세스 snapshot을 재사용하고, 관리자 sync/CLI만 <code>refresh=True</code>로 repo 파일을 다시 읽게 했다. 학습자 <code>SessionReview</code> 데스크톱은 좌측 축어록 타임라인과 우측 요약·감정·흐름·루브릭·강점·개선점·pre/post·워크시트·피드백 작업열의 2열 구조로 재배치했고, 교수자 검토 화면과 모바일/태블릿은 기존 반응형 규칙을 유지한다.</p></div><div class="kv"><b>검증</b><p><code>python -m compileall app -q</code>, 과거 <code>pytest app/test_live_coach_sources.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q</code> 27 passed, source sync focused <code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_live_coach_sources.py -q</code> 9 passed, H2 related <code>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</code> 44 passed, <code>py -3.11 -X utf8 scripts\sync-persona-sources.py --help</code>, <code>py -3.11 -X utf8 -m py_compile</code> focused files, <code>npm run generate:api-types</code>, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>, <code>npm run e2e -- e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts</code> 15 passed. 이번 2열 UI 검증: <code>npm run typecheck</code>, <code>npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1</code> 3 passed, <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code> 9 passed, <code>session-review__1440-desktop.png</code> 직접 확인. 로컬 DB smoke: source pack sync 4 sources/15 chunks embedded, 재호출 <code>skipped_unchanged=4</code>, <code>official_suicide_risk_guidelines</code> source filter 검색 성공.</p></div><div class="kv k-warn"><b>잔여</b><p>임상팀 골든셋·라이브 코칭 루브릭 검수, 공식 지침/논문 source pack 추가와 임상 검수 상태 운영정책 보강이 남아 있다.</p></div></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="scard" data-status="done" data-cat="콘텐츠·산출물" data-owner="0">
|
<article class="scard" data-status="done" data-cat="콘텐츠·산출물" data-owner="0">
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">HIGH H3 10차 DONE</span><span class="scard-mid"><span class="scard-title">페르소나 스튜디오 + RAG 첨부 + 항목형 저작/검토</span><span class="scard-sum">JSON 패널을 별도 저작 스튜디오로 분리하고, 실제 기록·교재 자료 기반 초안·목록형 임상 필드·라벨형 프롬프트 검토를 제공한다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-done">HIGH H3 10차 DONE</span><span class="scard-mid"><span class="scard-title">페르소나 스튜디오 + RAG 첨부 + 항목형 저작/검토</span><span class="scard-sum">JSON 패널을 별도 저작 스튜디오로 분리하고, 실제 기록·교재 자료 기반 초안·목록형 임상 필드·라벨형 프롬프트 검토를 제공한다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
|
|
@ -672,11 +682,11 @@
|
||||||
</article>
|
</article>
|
||||||
<article class="scard" data-status="done" data-cat="보안·PII" data-owner="0">
|
<article class="scard" data-status="done" data-cat="보안·PII" data-owner="0">
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">H4 부분 DONE</span><span class="scard-mid"><span class="scard-title">PII 마스킹 + LLM 호출 감사 + 프로필 온보딩·동의 게이트</span><span class="scard-sum">metadata-only llm_call_log, 15-case summary-only 마스킹 평가 계약, 전역 URL 하드게이트.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-done">H4 부분 DONE</span><span class="scard-mid"><span class="scard-title">PII 마스킹 + LLM 호출 감사 + 프로필 온보딩·동의 게이트</span><span class="scard-sum">metadata-only llm_call_log, 15-case summary-only 마스킹 평가 계약, 전역 URL 하드게이트.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
<div class="scard-body"><div class="kv"><b>현재</b><p>정규식 폴백에 한국어 고위험 패턴 추가: 구체 날짜(2001.4.18→[DATE])·금액(1200원→[MONEY])·행정구역 주소(서울시 강남구 역삼동→[ADDR])에 더해, 이름/성명 라벨·성씨+이름+조사/호칭·대학교/학과/병원/센터 등 suffix 기반 한국어 NAME/ORG 로컬 휴리스틱 1차를 추가했다. 이번 패스에서 <code>app.services.pii_masking_eval</code>, <code>data/privacy/pii-masking-ko-fixtures.json</code>, <code>scripts/evaluate-pii-masking.py</code>를 확장해 합성 fixture 15개로 NAME/ORG/PHONE/EMAIL/RRN/NUMID/DATE/MONEY/ADDR/negative-control을 <code>source</code>/<code>category</code>/<code>severity</code> metadata와 함께 entity recall·forbidden substring removal·unexpected entity violation으로 평가한다. <code>data/privacy/pii-masking-eval-input.schema.json</code>과 <code>data/privacy/pii-masking-eval-report.schema.json</code>은 Node.js 전환 후에도 공유 가능한 입력/리포트 계약이며, 기본 리포트는 <code>evidence_text_included=false</code>로 <code>masked_text</code>/<code>forbidden_remaining</code> 원문 증거를 제외한다. fixture가 잡은 <code>소속</code>/<code>안내</code>/<code>이름</code> NAME 오탐은 stopword로 보정했고, 66차에서는 자연 발화형 이름 라벨·자기소개 케이스를 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. Presidio가 설치돼도 한국어 누락을 막기 위해 regex fallback을 후단에 한 번 더 태운다. 상담 생성(generate/stream)·fast/deep 평가의 외부 LLM 호출 직후 <code>audit.llm_call_log</code>에 provider/model/token/cost/inference_geo/latency만 적재한다. prompt/completion 본문은 저장하지 않는다. fast evaluator prompt의 내담자 응답과 DB에 저장되는 client turn <code>text_masked</code>도 마스킹본을 쓰게 보강했다. 로컬 dev-login 실제 <code>/turn</code> smoke에서 <code>audit.llm_call_log</code>가 8→11로 3행 증가했다. 이번 패스에서 <code>app_user</code>에 이름·소속·학과·학년/직위·연락처·주소/수령지·닉네임·자기소개·아바타 URL·약관/개인정보 동의 시각·버전을 추가했다. 로그인 후 <code>onboarding_completed_at</code>이 없으면 전역 <code>OnboardingGate</code>가 <code>/onboarding</code> 외 URL(<code>/learn</code>, <code>/settings</code>, <code>/admin</code>, <code>/dev/avatar-preview</code>, <code>/login</code> 포함)을 모두 온보딩으로 돌리고, 온보딩 중에는 공용 셸 메뉴 없이 가입 직후 사용자 정보 입력 폼만 보여준다. learner 회기 시작도 온보딩 완료 전에는 막는다. <code>/users/me/avatar</code>는 PNG/JPG/WebP MIME·시그니처·3MB 제한 후 파일을 <code>USER_UPLOAD_DIR/profile-avatars</code>에 저장하고 URL만 보관한다. 약관·개인정보 처리방침 초안은 <code>/users/legal-docs</code>에서 제공한다. 관리자/교수자 권한은 온보딩에서 받지 않고 allowlist/관리자 사용자 관리로만 부여한다. 검증: <code>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</code> 43 passed, <code>py -3.11 -X utf8 scripts/evaluate-pii-masking.py --json</code> 15/15 pass, <code>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</code>, 과거 온보딩/동의 검증은 <code>pytest app/ -q</code> 178 passed, <code>pytest engine_gateway/ -q</code> 11 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>, auth E2E 7 passed, 최종 <code>PLAYWRIGHT_PORT=5174 npm run e2e</code> 113 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>한국어 이름/기관과 contact/national/quasi-identifier 합성 technical dry-run은 15-case summary-only harness까지 완료했지만, Presidio ko/spaCy ko/의료·학교 도메인 NER 기반 정밀화, 실제 운영 말뭉치 기반 오탐/미탐 평가, 미성년/guardian 및 법무 검토가 필요한 최종 서명 동의서·개인정보 처리방침·약관 evidence는 외부/후속 게이트다. 공개 Google OAuth 실제 <code>/turn</code> proof는 별도 운영 게이트.</p></div></div>
|
<div class="scard-body"><div class="kv"><b>현재</b><p>정규식 폴백에 한국어 고위험 패턴 추가: 구체 날짜(2001.4.18→[DATE])·금액(1200원→[MONEY])·행정구역 주소(서울시 강남구 역삼동→[ADDR])에 더해, 이름/성명 라벨·성씨+이름+조사/호칭·대학교/학과/병원/센터 등 suffix 기반 한국어 NAME/ORG 로컬 휴리스틱 1차를 추가했다. 이번 패스에서 <code>app.services.pii_masking_eval</code>, <code>data/privacy/pii-masking-ko-fixtures.json</code>, <code>scripts/evaluate-pii-masking.py</code>를 확장해 합성 fixture 15개로 NAME/ORG/PHONE/EMAIL/RRN/NUMID/DATE/MONEY/ADDR/negative-control을 <code>source</code>/<code>category</code>/<code>severity</code> metadata와 함께 entity recall·forbidden substring removal·unexpected entity violation으로 평가한다. <code>data/privacy/pii-masking-eval-input.schema.json</code>과 <code>data/privacy/pii-masking-eval-report.schema.json</code>은 Node.js 전환 후에도 공유 가능한 입력/리포트 계약이며, 기본 리포트는 <code>evidence_text_included=false</code>로 <code>masked_text</code>/<code>forbidden_remaining</code> 원문 증거를 제외한다. fixture가 잡은 <code>소속</code>/<code>안내</code>/<code>이름</code> NAME 오탐은 stopword로 보정했고, 66차에서는 자연 발화형 이름 라벨·자기소개 케이스를 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. Presidio가 설치돼도 한국어 누락을 막기 위해 regex fallback을 후단에 한 번 더 태운다. 70차에서는 <code>guardrail.mask_pii()</code> 내부에 선택형 한국어 PII recognizer adapter 경계를 추가했다. adapter는 명시 등록 전에는 비활성이고, 실패해도 예외를 밖으로 내보내지 않으며, fake adapter 테스트가 adapter span 마스킹 뒤 후단 regex fallback이 전화번호를 계속 처리하는지 고정한다. 상담 생성(generate/stream)·fast/deep 평가의 외부 LLM 호출 직후 <code>audit.llm_call_log</code>에 provider/model/token/cost/inference_geo/latency만 적재한다. prompt/completion 본문은 저장하지 않는다. fast evaluator prompt의 내담자 응답과 DB에 저장되는 client turn <code>text_masked</code>도 마스킹본을 쓰게 보강했다. 로컬 dev-login 실제 <code>/turn</code> smoke에서 <code>audit.llm_call_log</code>가 8→11로 3행 증가했다. 이번 패스에서 <code>app_user</code>에 이름·소속·학과·학년/직위·연락처·주소/수령지·닉네임·자기소개·아바타 URL·약관/개인정보 동의 시각·버전을 추가했다. 로그인 후 <code>onboarding_completed_at</code>이 없으면 전역 <code>OnboardingGate</code>가 <code>/onboarding</code> 외 URL(<code>/learn</code>, <code>/settings</code>, <code>/admin</code>, <code>/dev/avatar-preview</code>, <code>/login</code> 포함)을 모두 온보딩으로 돌리고, 온보딩 중에는 공용 셸 메뉴 없이 가입 직후 사용자 정보 입력 폼만 보여준다. learner 회기 시작도 온보딩 완료 전에는 막는다. <code>/users/me/avatar</code>는 PNG/JPG/WebP MIME·시그니처·3MB 제한 후 파일을 <code>USER_UPLOAD_DIR/profile-avatars</code>에 저장하고 URL만 보관한다. 약관·개인정보 처리방침 초안은 <code>/users/legal-docs</code>에서 제공한다. 관리자/교수자 권한은 온보딩에서 받지 않고 allowlist/관리자 사용자 관리로만 부여한다. 검증: <code>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</code> 47 passed, <code>py -3.11 -X utf8 scripts/evaluate-pii-masking.py --json</code> 15/15 pass, <code>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</code>, 과거 온보딩/동의 검증은 <code>pytest app/ -q</code> 178 passed, <code>pytest engine_gateway/ -q</code> 11 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>, auth E2E 7 passed, 최종 <code>PLAYWRIGHT_PORT=5174 npm run e2e</code> 113 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>한국어 이름/기관과 contact/national/quasi-identifier 합성 technical dry-run은 15-case summary-only harness 및 선택형 ko recognizer adapter 배선 회귀까지 완료했지만, 실제 운영 모델/provider 선정, 운영 말뭉치 기반 오탐/미탐 평가, 미성년/guardian 및 법무 검토가 필요한 최종 서명 동의서·개인정보 처리방침·약관 evidence는 외부/후속 게이트다. 공개 Google OAuth 실제 <code>/turn</code> proof는 별도 운영 게이트.</p></div></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="scard" data-status="planned" data-cat="리팩터·정리" data-owner="0">
|
<article class="scard" data-status="planned" data-cat="리팩터·정리" data-owner="0">
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-plan">MED M1~M3</span><span class="scard-mid"><span class="scard-title">비언어 임상이벤트(M1)·다회기 케이스 아크(M2)·SSO claim/식별자/감사(M3)</span><span class="scard-sum">M1 provider_events 보존+taxonomy+리뷰 파생 노출 4차, M2 case memory+pinned_fact/history/명시철회 contradiction+episodic embedding writer 6차 완료. M3는 cohort map + provider external_id 1차 완료, 기관 SAML/거버넌스는 남음.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-plan">MED M1~M3</span><span class="scard-mid"><span class="scard-title">비언어 임상이벤트(M1)·다회기 케이스 아크(M2)·SSO claim/식별자/감사(M3)</span><span class="scard-sum">M1 provider_events 보존+taxonomy+리뷰 파생 노출 4차, M2 case memory+pinned_fact/history/명시철회 contradiction+episodic embedding writer+다음 턴 주입 증거 7차 + digest 계약 경계 완료. M3는 cohort map + provider external_id 1차 완료, 기관 SAML/거버넌스는 남음.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
<div class="scard-body"><div class="kv"><b>현재</b><p>M1: voice learner turn의 audio_ref, silence_ms, speech_rate, barge_in을 리뷰 API <code>nonverbal</code> 이벤트로 파생하고, 회기 리뷰 축어록에 침묵·발화 속도·끼어듦·음성 입력 칩으로 표시한다. 2차로 <code>provider_events</code> JSONB 보존 슬롯을 추가해 control/STT provider 이벤트를 allowlist·size limit 후 저장한다. 3차에서는 저장 이벤트에 내부 taxonomy <code>event_type</code>/<code>category</code>를 추가해 sigh/cry/laugh/voice_activity/noise 등 canonical key를 남긴다. 4차에서는 인증된 회기 리뷰에 한숨·울음·웃음·호흡·운율·배경소음 계열만 한글 label/detail 칩으로 파생 노출한다. raw transcript/text payload, provider/source/raw type, 공개 공유 카드 노출은 제외했다. 실제 provider 기반 한숨·울음 감지와 live 마이크/WSS 장시간 실측은 아직 별도다. M2: <code>case_profile</code>을 learner-persona 안정 case로 upsert하고, <code>session_no</code>를 트랜잭션 안에서 원자 증가시킨다. 세션 종료 시 마스킹된 client-visible 축어록으로 fallback <code>session_summary.digest</code>와 case-level <code>case_digest</code>를 갱신하고, <code>rapport_trajectory</code>/<code>alliance_level</code>을 결정론 상태 수치에서 갱신한다. 종료 시점에는 마스킹된 client-visible 발화에서 <code>[NAME]</code>/<code>[ORG]</code> identity와 명시적 상담 약속만 <code>pinned_fact</code>로 upsert한다. 삽입 또는 값 변경 시에는 <code>pinned_fact_history</code>에 append-only 이력을 남기고, 같은 값 재확인과 <code>locked</code> fact는 이력을 늘리지 않는다. 5차에서는 명시적 상담 약속 철회/부정 문장만 기존 non-locked <code>agreement:counseling</code> fact를 <code>contradicted</code>로 격리하고 history reason <code>contradiction</code>을 남긴다. 새 contradicted fact를 임의 생성하거나 관계갈등·위기·임상 추론을 자동 모순 처리하지 않는다. 6차에서는 종료 저장 성공 뒤 background task로 마스킹된 client-visible 내담자 발화만 <code>app.turn_embedding</code>에 idempotent 색인한다. 시작/턴/voice 경로는 다음 회기 seed recall에서 <code>case_digest</code>, 직전 <code>session_summary</code>, client-visible non-contradicted <code>pinned_fact</code>, episodic recall 단편을 조립해 사용한다. M3: Google/SAML/dev-login이 <code>AUTH_EMAIL_COHORT_MAP</code>/<code>AUTH_DOMAIN_COHORT_MAP</code> 및 SAML cohort claim을 <code>cohort_ids</code>로 전달하고, DB <code>app_user.external_id</code>는 provider subject(<code>google:</code>/<code>saml:</code>/<code>dev:</code>) 기반으로 저장한다.</p></div><div class="kv k-warn"><b>권고</b><p>M1 실제 provider 기반 한숨·울음/억양 감지, 장시간 마이크/WSS 실측과 역량 지표화 정책, M2 관계/임상 fact 승격 기준·LLM digest 압축, M3 운영 SAML 서명검증·기관 claim schema/test tenant·deprovisioning audit는 후속이다.</p></div></div>
|
<div class="scard-body"><div class="kv"><b>현재</b><p>M1: voice learner turn의 audio_ref, silence_ms, speech_rate, barge_in을 리뷰 API <code>nonverbal</code> 이벤트로 파생하고, 회기 리뷰 축어록에 침묵·발화 속도·끼어듦·음성 입력 칩으로 표시한다. 2차로 <code>provider_events</code> JSONB 보존 슬롯을 추가해 control/STT provider 이벤트를 allowlist·size limit 후 저장한다. 3차에서는 저장 이벤트에 내부 taxonomy <code>event_type</code>/<code>category</code>를 추가해 sigh/cry/laugh/voice_activity/noise 등 canonical key를 남긴다. 4차에서는 인증된 회기 리뷰에 한숨·울음·웃음·호흡·운율·배경소음 계열만 한글 label/detail 칩으로 파생 노출한다. raw transcript/text payload, provider/source/raw type, 공개 공유 카드 노출은 제외했다. 실제 provider 기반 한숨·울음 감지와 live 마이크/WSS 장시간 실측은 아직 별도다. M2: <code>case_profile</code>을 learner-persona 안정 case로 upsert하고, <code>session_no</code>를 트랜잭션 안에서 원자 증가시킨다. 세션 종료 시 마스킹된 client-visible 축어록으로 fallback <code>session_summary.digest</code>와 case-level <code>case_digest</code>를 갱신하고, <code>rapport_trajectory</code>/<code>alliance_level</code>을 결정론 상태 수치에서 갱신한다. 종료 시점에는 마스킹된 client-visible 발화에서 <code>[NAME]</code>/<code>[ORG]</code> identity와 명시적 상담 약속만 <code>pinned_fact</code>로 upsert한다. 삽입 또는 값 변경 시에는 <code>pinned_fact_history</code>에 append-only 이력을 남기고, 같은 값 재확인과 <code>locked</code> fact는 이력을 늘리지 않는다. 5차에서는 명시적 상담 약속 철회/부정 문장만 기존 non-locked <code>agreement:counseling</code> fact를 <code>contradicted</code>로 격리하고 history reason <code>contradiction</code>을 남긴다. 새 contradicted fact를 임의 생성하거나 관계갈등·위기·임상 추론을 자동 모순 처리하지 않는다. 6차에서는 종료 저장 성공 뒤 background task로 마스킹된 client-visible 내담자 발화만 <code>app.turn_embedding</code>에 idempotent 색인한다. 7차에서는 시작 경로가 seed recall을 cache에 적재하고, REST submit/SSE stream은 공통 <code>_prepare_turn_context()</code>에서 recall, pinned fact, 최근 client-visible 턴, KB cue를 TurnContext/EngineMessage로 조립한다. route-level 테스트는 DB <code>case_digest</code>·직전 <code>session_summary</code>·client-visible <code>pinned_fact</code>가 다음 턴 L2/L4 블록에 들어가고 raw 이름이 <code>[NAME]</code>으로 마스킹되는지 고정한다. 76차에서는 <code>SessionDigestInput</code>/<code>SessionDigestResult</code>/<code>SessionSummaryWrite</code>로 종료 digest 입력·fallback 결과·DB write 인자 경계를 명시했다. future LLM digest worker 입력은 client-visible masked turn과 open thread만 받으며, 종료 상태 수치·평가 payload·CCD·raw text는 압축 prompt에 넣지 않는다. <code>digest_pending</code>은 CompressionJob 생성 여부 신호로 유지한다. M3: Google/SAML/dev-login이 <code>AUTH_EMAIL_COHORT_MAP</code>/<code>AUTH_DOMAIN_COHORT_MAP</code> 및 SAML cohort claim을 <code>cohort_ids</code>로 전달하고, DB <code>app_user.external_id</code>는 provider subject(<code>google:</code>/<code>saml:</code>/<code>dev:</code>) 기반으로 저장한다.</p></div><div class="kv k-warn"><b>권고</b><p>M1 실제 provider 기반 한숨·울음/억양 감지, 장시간 마이크/WSS 실측과 역량 지표화 정책, M2 관계/임상 fact 승격 기준·LLM digest worker 실행/품질평가/재압축, M3 운영 SAML 서명검증·기관 claim schema/test tenant·deprovisioning audit는 후속이다.</p></div></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="scard" data-status="planned" data-cat="거버넌스·아키텍처" data-owner="1">
|
<article class="scard" data-status="planned" data-cat="거버넌스·아키텍처" data-owner="1">
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-plan">TRACK X1·X2 2차·L1</span><span class="scard-mid"><span class="scard-title">재귀학습 export(X1)·API 비용 관측(X2)·스택 정합성·일정(L1)<span class="ownbadge">내 차례</span></span><span class="scard-sum">X1 dry-run export, X2 비용 관측·모델 override·evaluator semantic cache·모델별 비용 리포트 완료. L1은 Node 방향 결정, gateway 계약, schema-backed golden fixture, Node conformance runner, session/persona read-model 분리 완료. 등재 문서 반영은 외부 gate.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-plan">TRACK X1·X2 2차·L1</span><span class="scard-mid"><span class="scard-title">재귀학습 export(X1)·API 비용 관측(X2)·스택 정합성·일정(L1)<span class="ownbadge">내 차례</span></span><span class="scard-sum">X1 dry-run export, X2 비용 관측·모델 override·evaluator semantic cache·모델별 비용 리포트 완료. L1은 Node 방향 결정, gateway 계약, schema-backed golden fixture, Node conformance runner, session/persona read-model 분리 완료. 등재 문서 반영은 외부 gate.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
|
|
@ -684,9 +694,9 @@
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-blocks">
|
<div class="detail-blocks">
|
||||||
<div class="detail-block"><h4>즉시 착수 가능(내부 코드, 외부 합의 불요)</h4><ul><li>C1은 저장형 워크시트와 외부 루브릭 schema/loader/validation scaffold까지 3차 완료. C2는 1차 구조 완료, C3은 이론모드 배선과 명시 선택 UI까지 2차 완료, H2는 라이브 코칭 1차와 source pack version sync 및 학습자 리뷰 2열 UI 1차, H3은 페르소나 저작 CRUD/RAG 생성/seed materializer runner/source pack sync runner/raw source hash-only isolation/항목형 목록 저작 UI/프롬프트 미리보기 de-JSON까지 완료. 후속은 임상 스크립트, CBT 체인, 루브릭·이론 콘텐츠 외부화처럼 데이터·정책 고도화 영역이다.</li><li>H4(부분): 한국어 날짜/금액/주소 및 이름/기관 로컬 휴리스틱 마스킹, 15-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차에서 완료했다. 남은 내부 후보는 모델 기반 정밀 ko NER adapter 검토다. 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 6차, M3 claim 1차, L1 schema-backed engine gateway golden fixture·Node.js conformance runner·session/persona read-model 분리는 완료. 운영 SAML 서명검증은 기관 IdP 의존이다.</li></ul></div>
|
<div class="detail-block"><h4>즉시 착수 가능(내부 코드, 외부 합의 불요)</h4><ul><li>C1은 저장형 워크시트와 외부 루브릭 schema/loader/validation scaffold까지 3차 완료. C2는 1차 구조 완료, C3은 이론모드 배선과 명시 선택 UI까지 2차 완료, H2는 라이브 코칭 1차와 source pack version sync 및 학습자 리뷰 2열 UI 1차, H3은 페르소나 저작 CRUD/RAG 생성/seed materializer runner/source pack sync runner/raw source hash-only isolation/항목형 목록 저작 UI/프롬프트 미리보기 de-JSON까지 완료. 후속은 임상 스크립트, CBT 체인, 루브릭·이론 콘텐츠 외부화처럼 데이터·정책 고도화 영역이다.</li><li>H4(부분): 한국어 날짜/금액/주소 및 이름/기관 로컬 휴리스틱 마스킹, 15-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를 고르기 전에도 같은 <code>mask_pii()</code> 경계에서 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 의존이다.</li></ul></div>
|
||||||
<div class="detail-block"><h4>소유자 결정 / 외부(임상팀·기관) 의존</h4><ul><li>임상팀 산출물: C1 루브릭 항목·척도·가중치·anchor 예시·컷오프·검수 기준, C3 CBT 콘텐츠·체인, C2 위기 스크립트·서약 문안, H2 골든셋 코딩.</li><li>소유자 평가설계: H1 실험/통제군·3척도 문항·50분 흐름, κ/ICC·환각률 목표(doc4 미명시).</li><li>기관(한신 IT): M3 SSO IdP·test tenant·claim 스키마·인증서, 거버넌스 증거.</li><li>거버넌스: L1 Node 전환 방향은 결정됨. 남은 것은 FastAPI 유지 사유와 계약 우선 전환 계획의 저작권 등재 문서화·IP 협의.</li></ul></div>
|
<div class="detail-block"><h4>소유자 결정 / 외부(임상팀·기관) 의존</h4><ul><li>임상팀 산출물: C1 루브릭 항목·척도·가중치·anchor 예시·컷오프·검수 기준, C3 CBT 콘텐츠·체인, C2 위기 스크립트·서약 문안, H2 골든셋 코딩.</li><li>소유자 평가설계: H1 실험/통제군·3척도 문항·50분 흐름, κ/ICC·환각률 목표(doc4 미명시).</li><li>기관(한신 IT): M3 SSO IdP·test tenant·claim 스키마·인증서, 거버넌스 증거.</li><li>거버넌스: L1 Node 전환 방향은 결정됨. 남은 것은 FastAPI 유지 사유와 계약 우선 전환 계획의 저작권 등재 문서화·IP 협의.</li></ul></div>
|
||||||
<div class="detail-block"><h4>판독 한계 (정직성)</h4><ul><li>핵심 축어록 2단 표는 PDF 시각 판독이 1차 근거(txt 일부 누락), OCR 잔재 존재.</li><li>doc4 κ/ICC·환각률은 신청서 본문 미명시 — 계약 확정 지표로 단정 금지.</li><li>'현재상태'는 grep/코드 사실 기반에서 시작했으나 C1·C2·C3 2차 UI·H1~H4·M1 provider_events 보존/taxonomy/인증 리뷰 파생 노출·M2 case memory/pinned_fact/history/명시철회 contradiction/episodic embedding writer·M3·X1·X2·L1 Node conformance와 session/persona read-model 분리는 구현 후 재검증 완료 기준이다. H3 원문 격리는 hash-only artifact와 RAG index 차단까지이며, 암호화 blob/vault 원문 실저장은 아직 후속이다. 실제 provider 기반 한숨·울음 감지, H4 legal/guardian evidence, M3 운영 IdP, L1 외부 거버넌스는 아직 게이트다.</li></ul></div>
|
<div class="detail-block"><h4>판독 한계 (정직성)</h4><ul><li>핵심 축어록 2단 표는 PDF 시각 판독이 1차 근거(txt 일부 누락), OCR 잔재 존재.</li><li>doc4 κ/ICC·환각률은 신청서 본문 미명시 — 계약 확정 지표로 단정 금지.</li><li>'현재상태'는 grep/코드 사실 기반에서 시작했으나 C1·C2·C3 2차 UI·H1~H4·M1 provider_events 보존/taxonomy/인증 리뷰 파생 노출·M2 case memory/pinned_fact/history/명시철회 contradiction/episodic embedding writer/digest contract-only·M3·X1·X2·L1 Node conformance와 session/persona read-model 분리는 구현 후 재검증 완료 기준이다. M2는 fallback digest와 <code>digest_pending</code> 응답 계약까지이며, LLM digest worker 실행·품질평가·재압축은 완료로 보지 않는다. H3 원문 격리는 hash-only artifact와 RAG index 차단까지이며, 암호화 blob/vault 원문 실저장은 아직 후속이다. 실제 provider 기반 한숨·울음 감지, H4 legal/guardian evidence, M3 운영 IdP, L1 외부 거버넌스는 아직 게이트다.</li></ul></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -711,8 +721,8 @@
|
||||||
<div class="scard-body"><div class="kv"><b>현재</b><p><code>app/turn_runtime.py</code>를 추가해 <code>load_owned_session</code>, <code>record_completed_turn</code>, <code>append_completed_turn</code>, <code>update_session_state</code>, <code>record_safety_event</code>를 공통화했다. 이번 패스에서 <code>finalize_completed_turn</code>을 추가해 <code>record_completed_turn</code> + <code>record_safety_event</code> 호출쌍을 REST submit/stream과 voice WS 라우트에서 제거했다. stream fast-loop 평가 누락, voice 세션 recall cache, visible_to/evaluator turn 분리는 보존.</p></div><div class="kv"><b>검증</b><p><code>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</code> 40 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>더 큰 <code>finalize_turn</code>/<code>SessionRepository</code> 경계, 장기적으로는 세션 저장소 인터페이스 SSOT화가 남아 있다.</p></div></div>
|
<div class="scard-body"><div class="kv"><b>현재</b><p><code>app/turn_runtime.py</code>를 추가해 <code>load_owned_session</code>, <code>record_completed_turn</code>, <code>append_completed_turn</code>, <code>update_session_state</code>, <code>record_safety_event</code>를 공통화했다. 이번 패스에서 <code>finalize_completed_turn</code>을 추가해 <code>record_completed_turn</code> + <code>record_safety_event</code> 호출쌍을 REST submit/stream과 voice WS 라우트에서 제거했다. stream fast-loop 평가 누락, voice 세션 recall cache, visible_to/evaluator turn 분리는 보존.</p></div><div class="kv"><b>검증</b><p><code>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</code> 40 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>더 큰 <code>finalize_turn</code>/<code>SessionRepository</code> 경계, 장기적으로는 세션 저장소 인터페이스 SSOT화가 남아 있다.</p></div></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="scard" data-status="done" data-cat="리팩터·정리" data-owner="0">
|
<article class="scard" data-status="done" data-cat="리팩터·정리" data-owner="0">
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">REFACTOR 2 4차 DONE</span><span class="scard-mid"><span class="scard-title">백엔드 Pydantic ↔ 프론트 <code>lib/api.ts</code> 계약 SSOT화</span><span class="scard-sum">OpenAPI→<code>api.gen.ts</code> 생성 + CI stale 체크 게이트. 세션 계열 수기 DTO도 생성 타입 alias로 축소.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-done">REFACTOR 2 7차 DONE</span><span class="scard-mid"><span class="scard-title">백엔드 Pydantic ↔ 프론트 <code>lib/api.ts</code> 계약 SSOT화</span><span class="scard-sum">OpenAPI→<code>api.gen.ts</code> 생성 + CI stale 체크 게이트. 세션·리뷰·평가·교수·공유·live-coach stage 계약과 라벨 정규화를 union/null SSOT로 축소.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
<div class="scard-body"><div class="kv"><b>현재</b><p><code>apps/api/scripts/export-openapi.py</code>와 <code>apps/web/scripts/generate-api-types.mjs</code>로 FastAPI OpenAPI를 <code>apps/web/src/lib/api.gen.ts</code>에 생성한다. <code>npm run check:api-types</code>가 재생성 누락을 잡는다. <code>MeResponse</code>, <code>AuthConfigResponse</code>, <code>ConsentResponse</code>, <code>PersonaSummary</code>, <code>PersonaReviewSummary</code>, admin/user/engine DTO, review leaf DTO, <code>CrisisResourceResponse</code>, teacher safety/growth leaf DTO, <code>LearnerSessionsResponse</code>, <code>SessionReviewResponse</code>, worksheet aggregate, <code>TeacherDashboardResponse</code>, <code>TeacherSessionSummary</code>, <code>TeacherLearnerGrowth</code>에 이어 <code>SessionStartResponse</code>, <code>SessionDetailResponse</code>, <code>SessionDetailTurn</code>도 <code>ApiSchema<...></code> alias로 전환했다. 세션 응답 stage는 backend <code>StageLabel</code> enum으로 고정해 OpenAPI가 <code>라포|탐색|개입|정리</code> union을 생성한다. generated optional/default 차이는 화면 form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. <code>.github/workflows/api-contract.yml</code>은 PR/master push에서 같은 stale check를 실행해 OpenAPI type drift를 CI에서 막는다.</p></div><div class="kv"><b>검증</b><p><code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code> passed. <code>python -m pytest app/test_session_turn_persistence.py -q</code> 16 passed. 영향 범위 E2E: <code>learner.spec.ts</code> + <code>session-review.spec.ts</code> + <code>teacher.spec.ts</code> + <code>layout-visual-gate.spec.ts</code> 32 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>평가 조회·리뷰 도식·교수 콘솔 등 아직 자유 문자열로 남긴 stage-like 필드는 각 도메인 의미를 확인한 뒤 별도 축소한다.</p></div></div>
|
<div class="scard-body"><div class="kv"><b>현재</b><p><code>apps/api/scripts/export-openapi.py</code>와 <code>apps/web/scripts/generate-api-types.mjs</code>로 FastAPI OpenAPI를 <code>apps/web/src/lib/api.gen.ts</code>에 생성한다. <code>npm run check:api-types</code>가 재생성 누락을 잡는다. <code>MeResponse</code>, <code>AuthConfigResponse</code>, <code>ConsentResponse</code>, <code>PersonaSummary</code>, <code>PersonaReviewSummary</code>, admin/user/engine DTO, review leaf DTO, <code>CrisisResourceResponse</code>, teacher safety/growth leaf DTO, <code>LearnerSessionsResponse</code>, <code>LearnerDashboardResponse</code>, <code>SessionReviewResponse</code>, worksheet aggregate, <code>TeacherDashboardResponse</code>, <code>TeacherSessionSummary</code>, <code>TeacherLearnerGrowth</code>에 이어 <code>SessionStartResponse</code>, <code>SessionDetailResponse</code>, <code>SessionDetailTurn</code>도 <code>ApiSchema<...></code> alias로 전환했다. 세션 응답 stage는 backend <code>StageLabel</code> enum으로 고정해 OpenAPI가 <code>라포|탐색|개입|정리</code> union을 생성한다. 69차에서는 리뷰 도식 <code>ReviewPhaseSegment.key</code>를 <code>rapport|explore|intervene|closing</code>, <code>ReviewPhaseSegment.label</code>과 <code>SessionReviewResponse.reachedPhase</code>를 <code>StageLabel</code>로 좁혔다. 평가 조회 <code>EvaluationSummary.stage</code>는 평가 미생성 시 <code>null</code>, 평가 트리거 응답 stage와 교수 콘솔 <code>TeacherSessionSummary</code>/<code>TeacherGrowthPoint</code> stage는 <code>StageLabel</code> union으로 생성된다. 71차에서는 공개 공유 응답 <code>PublicSessionShareResponse.reachedPhase</code>와 live-coach 이력 <code>LiveCoachEvent.stage</code>도 <code>StageLabel | null</code> 계약으로 축소했다. 72차에서는 <code>app/stage_contract.py</code>가 <code>StageLabel</code>, <code>ReviewPhaseKey</code>, legacy stage code 정규화, unknown→null 판단, 리뷰 phase key 매핑을 소유하게 해 <code>turn_runtime.py</code>, <code>session_read_model.py</code>, <code>routes/eval.py</code>의 중복 규칙을 제거했다. generated optional/default 차이는 화면 form state, notification default helper, 배열 렌더링 fallback, live-coach <code>단계 미상</code> 표시로 흡수한다. <code>.github/workflows/api-contract.yml</code>은 PR/master push에서 같은 stale check를 실행해 OpenAPI type drift를 CI에서 막는다.</p></div><div class="kv"><b>검증</b><p><code>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</code> 50 passed. 공유/live-coach 후속 검증: <code>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</code> 43 passed. Stage contract SSOT 검증: <code>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</code> 42 passed. <code>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</code>, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code> passed. 과거 영향 범위 E2E: <code>learner.spec.ts</code> + <code>session-review.spec.ts</code> + <code>teacher.spec.ts</code> + <code>layout-visual-gate.spec.ts</code> 32 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>남은 자유 문자열 stage는 evaluator 내부 payload, DB 저장 컬럼, dataset export, runtime store 같은 저장·분석 payload다. 라벨/phase key 변환 규칙은 <code>app/stage_contract.py</code> 하나로 이동했고, 브라우저-facing OpenAPI 응답 stage 계약은 현재 확인된 session/review/eval/teacher/share/live-coach 표면에서 enum/null로 고정했다.</p></div></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="scard" data-status="done" data-cat="인프라·스택·RAG" data-owner="0">
|
<article class="scard" data-status="done" data-cat="인프라·스택·RAG" data-owner="0">
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">SEO/GEO 공유</span><span class="scard-mid"><span class="scard-title">검색 메타 + 회기 리뷰 URL 공유 카드</span><span class="scard-sum">robots/sitemap/llms + OG/JSON-LD. 종료 회기는 토큰 공유 URL로 교수자에게 요약·썸네일 전달.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-done">SEO/GEO 공유</span><span class="scard-mid"><span class="scard-title">검색 메타 + 회기 리뷰 URL 공유 카드</span><span class="scard-sum">robots/sitemap/llms + OG/JSON-LD. 종료 회기는 토큰 공유 URL로 교수자에게 요약·썸네일 전달.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
|
|
@ -803,7 +813,7 @@
|
||||||
<div class="task-list">
|
<div class="task-list">
|
||||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>LLMEngine 정규화 계약 고정</b><p>gateway SSE <code>token/done/error</code>와 generate cost 계약은 회귀화했다. 이번 패스에서 <code>app/contracts/engine_gateway.py</code>를 추가해 FastAPI <code>engine_client</code>와 현재 Python gateway가 같은 <code>GenerateRequest</code>/<code>GenerateResponse</code>/<code>Stream*Event</code> 계약을 import한다. 이어서 <code>EngineGatewaySseLineDecoder</code>로 token/done/error decode와 validation도 같은 계약 모듈에 모았다. 최신 패스에서는 raw SSE line 해석을 <code>EngineClient.stream_packets()</code> 경계로 올리고, Python gateway <code>/v1/generate</code>가 <code>GenerateResponse.model_dump()</code>로 응답하게 해 hand-mirrored dict drift를 줄였다. Node.js gateway로 갈아치울 때 보존해야 할 첫 wire contract다. claude_cli와 Messages API live parity는 <code>ANTHROPIC_API_KEY</code> 의존 B2 항목으로 분리한다.</p></div><div><b>산출물</b><p><code>app/contracts/engine_gateway.py</code>, <code>app/engine_client.py</code>, <code>app/services/orchestrator.py</code>, <code>engine_gateway/gateway.py</code>, <code>engine_gateway/test_gateway_model.py</code>, <code>engine_gateway/golden/*</code>, <code>scripts/check-engine-gateway-contract.mjs</code> shared contract + stream packet tests</p></div><div><b>검증</b><p><code>node scripts/check-engine-gateway-contract.mjs --json</code>, <code>python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py -q</code> 19 passed, <code>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</code> 50 passed.</p></div></div>
|
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>LLMEngine 정규화 계약 고정</b><p>gateway SSE <code>token/done/error</code>와 generate cost 계약은 회귀화했다. 이번 패스에서 <code>app/contracts/engine_gateway.py</code>를 추가해 FastAPI <code>engine_client</code>와 현재 Python gateway가 같은 <code>GenerateRequest</code>/<code>GenerateResponse</code>/<code>Stream*Event</code> 계약을 import한다. 이어서 <code>EngineGatewaySseLineDecoder</code>로 token/done/error decode와 validation도 같은 계약 모듈에 모았다. 최신 패스에서는 raw SSE line 해석을 <code>EngineClient.stream_packets()</code> 경계로 올리고, Python gateway <code>/v1/generate</code>가 <code>GenerateResponse.model_dump()</code>로 응답하게 해 hand-mirrored dict drift를 줄였다. Node.js gateway로 갈아치울 때 보존해야 할 첫 wire contract다. claude_cli와 Messages API live parity는 <code>ANTHROPIC_API_KEY</code> 의존 B2 항목으로 분리한다.</p></div><div><b>산출물</b><p><code>app/contracts/engine_gateway.py</code>, <code>app/engine_client.py</code>, <code>app/services/orchestrator.py</code>, <code>engine_gateway/gateway.py</code>, <code>engine_gateway/test_gateway_model.py</code>, <code>engine_gateway/golden/*</code>, <code>scripts/check-engine-gateway-contract.mjs</code> shared contract + stream packet tests</p></div><div><b>검증</b><p><code>node scripts/check-engine-gateway-contract.mjs --json</code>, <code>python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py -q</code> 19 passed, <code>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</code> 50 passed.</p></div></div>
|
||||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>상주 <code>claude -p</code> 엔진풀 실증</b><p><code>session_id</code>가 살아 있는 gateway session을 재사용하고 missing session은 ephemeral로 닫히는 구조를 회귀화했고, live 게이트웨이(9099)로 실측까지 마쳤다. <code>probe-engine-gateway.py</code>가 단일 session_id로 2회 reused stream을 돌려 TTFT 1667–4199ms, cost 누적 0.068→0.123(turns 1→2), engine=claude_p, model=opus-4-8을 측정했다.</p></div><div><b>산출물</b><p><code>engine_gateway/test_gateway_model.py</code>, <code>scripts/probe-engine-gateway.py</code>, live probe JSON</p></div><div><b>검증</b><p>session reuse 7 tests OK; live probe 2 reused streams, TTFT/cost 실측, session_id 재사용 확인</p></div></div>
|
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>상주 <code>claude -p</code> 엔진풀 실증</b><p><code>session_id</code>가 살아 있는 gateway session을 재사용하고 missing session은 ephemeral로 닫히는 구조를 회귀화했고, live 게이트웨이(9099)로 실측까지 마쳤다. <code>probe-engine-gateway.py</code>가 단일 session_id로 2회 reused stream을 돌려 TTFT 1667–4199ms, cost 누적 0.068→0.123(turns 1→2), engine=claude_p, model=opus-4-8을 측정했다.</p></div><div><b>산출물</b><p><code>engine_gateway/test_gateway_model.py</code>, <code>scripts/probe-engine-gateway.py</code>, live probe JSON</p></div><div><b>검증</b><p>session reuse 7 tests OK; live probe 2 reused streams, TTFT/cost 실측, session_id 재사용 확인</p></div></div>
|
||||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>마스킹 게이트 + LLM call audit</b><p>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 <code>text_masked</code>도 마스킹본을 쓰게 보강했다. 66차에서는 자연 발화형 이름 라벨·자기소개 케이스를 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 외부 LLM 호출은 <code>audit.llm_call_log</code>에 provider/model/token/cost/inference_geo/latency metadata만 남기고 prompt/completion 본문은 저장하지 않는다.</p></div><div><b>산출물</b><p><code>app/services/pii_masking_eval.py</code>, <code>data/privacy/pii-masking-ko-fixtures.json</code>, <code>data/privacy/pii-masking-eval-input.schema.json</code>, <code>data/privacy/pii-masking-eval-report.schema.json</code>, <code>scripts/evaluate-pii-masking.py</code>, <code>app/test_pii_masking_eval.py</code>, <code>app/test_orchestrator_masking.py</code>, <code>app/test_evaluation_persistence.py</code>, <code>app/test_session_turn_persistence.py</code>, <code>session_persistence.record_llm_call_audit</code></p></div><div><b>검증</b><p>phone/email/RRN 및 한국어 NAME/ORG raw 값이 generate/stream/evaluator payload에 없음, 감사 payload 본문 미포함, fixture 15/15 pass, focused 회귀 43 passed, 현재 전체 API 178 기준 유지, 로컬 live <code>/turn</code> smoke 후 <code>audit.llm_call_log</code> 8→11</p></div></div>
|
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>마스킹 게이트 + LLM call audit</b><p>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 <code>text_masked</code>도 마스킹본을 쓰게 보강했다. 66차에서는 자연 발화형 이름 라벨·자기소개 케이스를 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 70차에서는 선택형 ko recognizer adapter를 <code>mask_pii()</code> 경계에 추가하고, adapter 성공/실패 모두 regex fallback이 마지막 안전망으로 남는지 회귀화했다. 외부 LLM 호출은 <code>audit.llm_call_log</code>에 provider/model/token/cost/inference_geo/latency metadata만 남기고 prompt/completion 본문은 저장하지 않는다.</p></div><div><b>산출물</b><p><code>app/services/guardrail.py</code>, <code>app/services/pii_masking_eval.py</code>, <code>data/privacy/pii-masking-ko-fixtures.json</code>, <code>data/privacy/pii-masking-eval-input.schema.json</code>, <code>data/privacy/pii-masking-eval-report.schema.json</code>, <code>scripts/evaluate-pii-masking.py</code>, <code>app/test_pii_masking_eval.py</code>, <code>app/test_orchestrator_masking.py</code>, <code>app/test_evaluation_persistence.py</code>, <code>app/test_session_turn_persistence.py</code>, <code>session_persistence.record_llm_call_audit</code></p></div><div><b>검증</b><p>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 <code>/turn</code> smoke 후 <code>audit.llm_call_log</code> 8→11</p></div></div>
|
||||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>P1 상담 완주 플로우</b><p>로컬 DB-backed 런타임에서 P1 선택, SSE 상담, 종료, 회기말 리뷰 피드백을 한 번에 통과시켰다. 실제 운영 로그인 proof는 P0 public OAuth blocker와 분리해 남긴다.</p></div><div><b>산출물</b><p><code>e2e/session-mvp.spec.ts</code>, turn telemetry 저장 경로</p></div><div><b>검증</b><p><code>npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1</code> 1 passed.</p></div></div>
|
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>P1 상담 완주 플로우</b><p>로컬 DB-backed 런타임에서 P1 선택, SSE 상담, 종료, 회기말 리뷰 피드백을 한 번에 통과시켰다. 실제 운영 로그인 proof는 P0 public OAuth blocker와 분리해 남긴다.</p></div><div><b>산출물</b><p><code>e2e/session-mvp.spec.ts</code>, turn telemetry 저장 경로</p></div><div><b>검증</b><p><code>npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1</code> 1 passed.</p></div></div>
|
||||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>저항 엔진 정량 검증</b><p>공감 발화와 조언점프 발화 5턴 세트를 상태머신 회귀 테스트와 실제 API+DB smoke로 비교했다. live smoke는 dev-login→onboarding→P1 세션 2개→각 5턴을 수행하고 <code>app.session_state</code>, <code>app.turns</code>, <code>app.turn_client_state</code>를 직접 조회했다. 공감 세션은 3턴부터 <code>탐색</code>으로 열리고 openness가 <code>0.171</code>까지 상승했다. 조언점프 세션은 <code>라포</code>와 openness <code>0.0</code>을 유지했다.</p></div><div><b>산출물</b><p><code>app/test_state_machine_resistance.py</code>, <code>scripts/smoke-resistance-openness-db.py</code>, <code>docs/ops/resistance-openness-db-smoke-2026-06-28.json</code></p></div><div><b>검증</b><p><code>pytest app/test_state_machine_resistance.py app/test_evaluation_persistence.py -q</code> 9 passed; live DB smoke PASS.</p></div></div>
|
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>저항 엔진 정량 검증</b><p>공감 발화와 조언점프 발화 5턴 세트를 상태머신 회귀 테스트와 실제 API+DB smoke로 비교했다. live smoke는 dev-login→onboarding→P1 세션 2개→각 5턴을 수행하고 <code>app.session_state</code>, <code>app.turns</code>, <code>app.turn_client_state</code>를 직접 조회했다. 공감 세션은 3턴부터 <code>탐색</code>으로 열리고 openness가 <code>0.171</code>까지 상승했다. 조언점프 세션은 <code>라포</code>와 openness <code>0.0</code>을 유지했다.</p></div><div><b>산출물</b><p><code>app/test_state_machine_resistance.py</code>, <code>scripts/smoke-resistance-openness-db.py</code>, <code>docs/ops/resistance-openness-db-smoke-2026-06-28.json</code></p></div><div><b>검증</b><p><code>pytest app/test_state_machine_resistance.py app/test_evaluation_persistence.py -q</code> 9 passed; live DB smoke PASS.</p></div></div>
|
||||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>음성 왕복 1턴 PoC</b><p>Session UI 마이크 버튼 경로에서 synthetic <code>getUserMedia</code>/<code>MediaRecorder</code>로 실제 <code>/voice/ws</code>를 열고 audio_start, binary chunk, audio_end, transcript, reply, TTS 재생 상태까지 검증했다. 추가로 로컬 live API에서 실제 STT/TTS provider를 통과해 TTS 바이너리를 수신했다. 물리 마이크, 공개 WSS, 50분 양방향 실측은 별도 운영 smoke로 남아 있다.</p></div><div><b>산출물</b><p><code>apps/web/e2e/voice-success.spec.ts</code>, controlled OpenAI/engine providers, Web Audio browser voice UI probe, live <code>/voice/ws</code> smoke</p></div><div><b>검증</b><p><code>npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run</code> 2 passed; live P2 WS smoke binary 66 chunks/266,496 bytes</p></div></div>
|
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>음성 왕복 1턴 PoC</b><p>Session UI 마이크 버튼 경로에서 synthetic <code>getUserMedia</code>/<code>MediaRecorder</code>로 실제 <code>/voice/ws</code>를 열고 audio_start, binary chunk, audio_end, transcript, reply, TTS 재생 상태까지 검증했다. 추가로 로컬 live API에서 실제 STT/TTS provider를 통과해 TTS 바이너리를 수신했다. 물리 마이크, 공개 WSS, 50분 양방향 실측은 별도 운영 smoke로 남아 있다.</p></div><div><b>산출물</b><p><code>apps/web/e2e/voice-success.spec.ts</code>, controlled OpenAI/engine providers, Web Audio browser voice UI probe, live <code>/voice/ws</code> smoke</p></div><div><b>검증</b><p><code>npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run</code> 2 passed; live P2 WS smoke binary 66 chunks/266,496 bytes</p></div></div>
|
||||||
|
|
@ -865,29 +875,29 @@
|
||||||
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr>
|
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr>
|
||||||
<tr><td>Full Playwright E2E</td><td><code>PLAYWRIGHT_PORT=5174 npm run e2e</code></td><td>113 passed; desktop/mobile 병렬 + <code>@single-run</code> DB 영속화·음성 성공 경로까지 전체 통과.</td></tr>
|
<tr><td>Full Playwright E2E</td><td><code>PLAYWRIGHT_PORT=5174 npm run e2e</code></td><td>113 passed; desktop/mobile 병렬 + <code>@single-run</code> DB 영속화·음성 성공 경로까지 전체 통과.</td></tr>
|
||||||
<tr><td>API typegen SSOT</td><td><code>npm run check:api-types</code></td><td>Passed; FastAPI OpenAPI → <code>src/lib/api.gen.ts</code> stale check</td></tr>
|
<tr><td>API typegen SSOT</td><td><code>npm run check:api-types</code></td><td>Passed; FastAPI OpenAPI → <code>src/lib/api.gen.ts</code> stale check</td></tr>
|
||||||
<tr><td>Contract SSOT aggregate DTO</td><td><code>npm run typecheck</code> / <code>npm run build</code> / <code>session persistence</code> / <code>learner + session-review + teacher + layout gate</code></td><td>Passed; learner sessions, session review/worksheet, teacher dashboard, session start/detail DTOs now use generated <code>ApiSchema</code> aliases with UI fallback. Stage responses are OpenAPI enum unions. Focused E2E 32 passed. <code>.github/workflows/api-contract.yml</code> now runs <code>npm run check:api-types</code> on API/Web contract changes.</td></tr>
|
<tr><td>Contract SSOT aggregate DTO</td><td><code>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</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code> / <code>npm run build</code></td><td>50 backend passed; learner sessions, session review/worksheet, teacher dashboard, session start/detail DTOs use generated <code>ApiSchema</code> 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. <code>.github/workflows/api-contract.yml</code> runs <code>npm run check:api-types</code> on API/Web contract changes.</td></tr>
|
||||||
<tr><td>SEO/GEO share cards</td><td><code>pytest app/test_session_share.py app/test_session_turn_persistence.py -q</code> / <code>npm run generate:api-types</code> / <code>npm run typecheck</code></td><td>21 passed; session share creates hashed-token public unfurl payload without raw transcript, revoked token returns 404, OpenAPI generated share DTOs, review screen share button typechecks. Static <code>robots.txt</code>/<code>sitemap.xml</code>/<code>llms.txt</code> added.</td></tr>
|
<tr><td>SEO/GEO share cards</td><td><code>pytest app/test_session_share.py app/test_session_turn_persistence.py -q</code> / <code>npm run generate:api-types</code> / <code>npm run typecheck</code></td><td>21 passed; session share creates hashed-token public unfurl payload without raw transcript, revoked token returns 404, OpenAPI generated share DTOs, review screen share button typechecks. Static <code>robots.txt</code>/<code>sitemap.xml</code>/<code>llms.txt</code> added.</td></tr>
|
||||||
<tr><td>Backend pytest</td><td><code>python -m pytest app/ -q</code></td><td>178 passed</td></tr>
|
<tr><td>Backend pytest</td><td><code>python -m pytest app/ -q</code></td><td>178 passed</td></tr>
|
||||||
<tr><td>X2 evaluator routing/cache/cost trend</td><td><code>python -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</code> / <code>scripts/report-ai-usage.py</code> sample CLI / prior web gates</td><td>42 backend passed + CLI sample OK; fast/deep evaluator model override, semantic cache hit/miss, admin usage budget/fail-closed, cache hit-rate observability, daily cost trend, and provider/model cost report schema covered. Prior API typecheck/build/admin E2E evidence remains valid because the API response shape was not changed.</td></tr>
|
<tr><td>X2 evaluator routing/cache/cost trend</td><td><code>python -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</code> / <code>scripts/report-ai-usage.py</code> sample CLI / prior web gates</td><td>42 backend passed + CLI sample OK; fast/deep evaluator model override, semantic cache hit/miss, admin usage budget/fail-closed, cache hit-rate observability, daily cost trend, and provider/model cost report schema covered. Prior API typecheck/build/admin E2E evidence remains valid because the API response shape was not changed.</td></tr>
|
||||||
<tr><td>H3 raw source isolation</td><td><code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py app/test_live_coach_sources.py -q</code></td><td>38 passed; persona source registration records raw hash-only artifacts separately from sanitized evaluator-only RAG chunks, and <code>rag.index_document()</code> rejects <code>sensitivity=3</code>/raw-marker chunks before DB access.</td></tr>
|
<tr><td>H3 raw source isolation</td><td><code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py app/test_live_coach_sources.py -q</code></td><td>38 passed; persona source registration records raw hash-only artifacts separately from sanitized evaluator-only RAG chunks, and <code>rag.index_document()</code> rejects <code>sensitivity=3</code>/raw-marker chunks before DB access.</td></tr>
|
||||||
<tr><td>H3 persona itemized authoring UI</td><td><code>npm run typecheck</code> / <code>npm run build</code> / <code>npm run check:api-types</code> / <code>npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code> / <code>npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1</code></td><td>Typecheck/build/API type drift check passed; teacher PersonaStudio focused 8 passed including structured list payload and labeled prompt preview without raw JSON; visual gate 9 passed with the prompt tab opened; session-layout desktop/mobile 8 passed.</td></tr>
|
<tr><td>H3 persona itemized authoring UI</td><td><code>npm run typecheck</code> / <code>npm run build</code> / <code>npm run check:api-types</code> / <code>npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code> / <code>npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1</code></td><td>Typecheck/build/API type drift check passed; teacher PersonaStudio focused 8 passed including structured list payload and labeled prompt preview without raw JSON; visual gate 9 passed with the prompt tab opened; session-layout desktop/mobile 8 passed.</td></tr>
|
||||||
<tr><td>H2 session review 2-column UX</td><td><code>npm run typecheck</code> / <code>npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code></td><td>Typecheck passed; 3 focused E2E passed; 9 visual gates passed. Filled learner review uses a left transcript rail and right work column at desktop widths while teacher/mobile layouts remain on existing rules.</td></tr>
|
<tr><td>H2 session review 2-column UX</td><td><code>npm run typecheck</code> / <code>npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code></td><td>Typecheck passed; 3 focused E2E passed; 9 visual gates passed. Filled learner review uses a left transcript rail and right work column at desktop widths while teacher/mobile layouts remain on existing rules.</td></tr>
|
||||||
<tr><td>M2 case memory</td><td><code>python -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 -q</code></td><td>67 passed; session end writes masked fallback digest, case_digest, rapport trajectory, alliance EWMA, conservative identity/agreement pinned_fact upserts, append-only pinned_fact_history on insert/change, explicit counseling-agreement withdrawal marks the existing non-locked fact as contradicted, and episodic writer stores only masked client-visible client turns into <code>app.turn_embedding</code> idempotently.</td></tr>
|
<tr><td>M2 case memory</td><td><code>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</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code></td><td>74 backend passed; session end writes masked fallback digest, case_digest, rapport trajectory, alliance EWMA, conservative identity/agreement pinned_fact upserts, append-only pinned_fact_history on insert/change, explicit counseling-agreement withdrawal marks the existing non-locked fact as contradicted, episodic writer stores only masked client-visible client turns into <code>app.turn_embedding</code> idempotently, next-session route-level turn proof shows DB seed recall/pinned facts entering EngineMessage L2/L4 with raw names masked, and <code>SessionDigestInput</code>/<code>SessionDigestResult</code>/<code>SessionSummaryWrite</code> keep digest worker input/output and session_summary write shape contract-only. <code>SessionEndResponse.digest_pending</code> remains a boolean compression-needed signal in OpenAPI/generated types.</td></tr>
|
||||||
<tr><td>L1 session read-model split</td><td><code>python -B -m py_compile app/session_read_model.py app/routes/sessions.py</code> / <code>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</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code></td><td>py_compile passed; focused backend 48 passed; OpenAPI generated API type check and web typecheck passed. <code>app/session_read_model.py</code> now owns session list/dashboard/detail/review/share DTOs and deterministic builders while <code>routes/sessions.py</code> keeps auth, RLS-backed reads, persistence, and route decorators.</td></tr>
|
<tr><td>L1 session read-model split</td><td><code>python -B -m py_compile app/session_read_model.py app/routes/sessions.py</code> / <code>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</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code></td><td>py_compile passed; focused backend 48 passed; OpenAPI generated API type check and web typecheck passed. <code>app/session_read_model.py</code> now owns session list/dashboard/detail/review/share DTOs and deterministic builders while <code>routes/sessions.py</code> keeps auth, RLS-backed reads, persistence, and route decorators.</td></tr>
|
||||||
<tr><td>L1 persona read-model split</td><td><code>python -B -m py_compile app/persona_read_model.py app/routes/personas.py</code> / <code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py -q</code> / <code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py app/test_session_turn_persistence.py -q</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code></td><td>py_compile passed; persona review/backend 33 passed; persona + session-start boundary 55 passed; OpenAPI generated API type check and web typecheck passed. <code>app/persona_read_model.py</code> now owns persona catalog/review/draft/source/evidence DTOs and deterministic mappers while <code>routes/personas.py</code> keeps auth, teacher/admin gates, DB repository calls, RAG source registration, LLM draft generation, and HTTP error mapping.</td></tr>
|
<tr><td>L1 persona read-model split</td><td><code>python -B -m py_compile app/persona_read_model.py app/routes/personas.py</code> / <code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py -q</code> / <code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py app/test_session_turn_persistence.py -q</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code></td><td>py_compile passed; persona review/backend 33 passed; persona + session-start boundary 55 passed; OpenAPI generated API type check and web typecheck passed. <code>app/persona_read_model.py</code> now owns persona catalog/review/draft/source/evidence DTOs and deterministic mappers while <code>routes/personas.py</code> keeps auth, teacher/admin gates, DB repository calls, RAG source registration, LLM draft generation, and HTTP error mapping.</td></tr>
|
||||||
<tr><td>M1 provider_events preservation</td><td><code>python -B -m pytest -p no:cacheprovider app/test_voice_ws.py app/test_session_turn_persistence.py app/test_voice_service.py -q</code></td><td>45 passed; voice control/STT provider events are allowlisted, size-limited, and tagged with internal <code>event_type</code>/<code>category</code> taxonomy into <code>app.turns.provider_events</code>. 인증된 review <code>nonverbal</code>은 정규화 taxonomy 일부만 파생 칩으로 노출하며 raw transcript/text/provider/source/raw type은 응답하지 않는다.</td></tr>
|
<tr><td>M1 provider_events preservation</td><td><code>python -B -m pytest -p no:cacheprovider app/test_voice_ws.py app/test_session_turn_persistence.py app/test_voice_service.py -q</code></td><td>45 passed; voice control/STT provider events are allowlisted, size-limited, and tagged with internal <code>event_type</code>/<code>category</code> taxonomy into <code>app.turns.provider_events</code>. 인증된 review <code>nonverbal</code>은 정규화 taxonomy 일부만 파생 칩으로 노출하며 raw transcript/text/provider/source/raw type은 응답하지 않는다.</td></tr>
|
||||||
<tr><td>C1 case worksheet</td><td><code>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</code> / <code>scripts/check-case-worksheet-rubric.py --json</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code> / <code>npm run build</code> / local API smoke</td><td>34 passed for worksheet persistence/RBAC plus rubric scaffold; session review exposes transcript-grounded <code>caseWorksheet</code>, learner-edited <code>saved_by_learner</code> worksheet persists through <code>app.case_worksheet</code>, and <code>case-worksheet-rubric@scaffold-2026-06-28</code> validates 5 sections/28 items with <code>scoring_enabled=false</code>. Local smoke returned <code>saved_by_learner:local smoke saved worksheet</code>. Stage labels are normalized at response boundaries.</td></tr>
|
<tr><td>C1 case worksheet</td><td><code>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</code> / <code>scripts/check-case-worksheet-rubric.py --json</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code> / <code>npm run build</code> / <code>session-review.spec.ts</code> / local API smoke</td><td>40 passed for worksheet persistence/RBAC/teacher review plus rubric scaffold; session review exposes transcript-grounded <code>caseWorksheet</code>, learner-edited <code>saved_by_learner</code> worksheet persists through <code>app.case_worksheet</code>, teacher/admin <code>teacherReview</code> exposes manual <code>worksheetStatus</code>/<code>worksheetNote</code>/<code>worksheetReviewedAt</code>, and <code>case-worksheet-rubric@scaffold-2026-06-28</code> validates 5 sections/28 items with <code>scoring_enabled=false</code>. Local smoke returned <code>saved_by_learner:local smoke saved worksheet</code>. Stage labels are normalized at response boundaries.</td></tr>
|
||||||
<tr><td>M3 auth claim mapping</td><td><code>python -B -m pytest -p no:cacheprovider app/test_auth_providers.py -q</code></td><td>30 passed; Google/SAML cohort maps, provider external_id, provider error reason, PKCE/state, HttpOnly cookie, SAML fixture ACS covered</td></tr>
|
<tr><td>M3 auth claim mapping</td><td><code>python -B -m pytest -p no:cacheprovider app/test_auth_providers.py -q</code></td><td>30 passed; Google/SAML cohort maps, provider external_id, provider error reason, PKCE/state, HttpOnly cookie, SAML fixture ACS covered</td></tr>
|
||||||
<tr><td>CrisisGate 109</td><td><code>python -m pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q</code></td><td>20 passed; real crisis stops before engine, returns 109 resource, safety event DB insert payload covered</td></tr>
|
<tr><td>CrisisGate 109</td><td><code>python -m pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q</code></td><td>20 passed; real crisis stops before engine, returns 109 resource, safety event DB insert payload covered</td></tr>
|
||||||
<tr><td>Professor review workflow</td><td><code>python -m pytest app/test_rbac_idor.py app/test_teacher_dashboard.py -q</code> / <code>npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1</code></td><td>9 backend passed + 4 E2E passed; teacher dashboard exposes learner_growth/safety queues and review-pending/recent rows now open <code>/teach/session/:id/review</code> read-only with learner worksheet editing blocked for teachers.</td></tr>
|
<tr><td>Professor review workflow</td><td><code>python -m pytest app/test_rbac_idor.py app/test_teacher_dashboard.py -q</code> / <code>npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1</code></td><td>9 backend passed + 4 E2E passed; teacher dashboard exposes learner_growth/safety queues and review-pending/recent rows now open <code>/teach/session/:id/review</code> read-only with learner worksheet editing blocked for teachers.</td></tr>
|
||||||
<tr><td>Learner dashboard personalization</td><td><code>pytest app/test_learner_dashboard.py app/test_teacher_dashboard.py app/test_rbac_idor.py -q</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code> / <code>npm run build</code></td><td>12 backend passed; <code>GET /sessions/dashboard</code> uses shared <code>session_metrics</code> and feeds LearnerHome overview/growth/persona_progress/recent_feedback/last-session recap. Focused learner/review/teacher/layout/session E2E evidence recorded in backlog.</td></tr>
|
<tr><td>Learner dashboard personalization</td><td><code>pytest app/test_learner_dashboard.py app/test_teacher_dashboard.py app/test_rbac_idor.py -q</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code> / <code>npm run build</code></td><td>12 backend passed; <code>GET /sessions/dashboard</code> uses shared <code>session_metrics</code> and feeds LearnerHome overview/growth/persona_progress/recent_feedback/last-session recap. Focused learner/review/teacher/layout/session E2E evidence recorded in backlog.</td></tr>
|
||||||
<tr><td>Teacher review status</td><td><code>pytest app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_learner_dashboard.py -q</code> / <code>npm run typecheck</code> / <code>npm run build</code> / <code>teacher.spec.ts</code></td><td>12 backend passed + 4 E2E passed; <code>app.session_review_status</code>, teacher dashboard <code>review_status/review_note/reviewed_at</code>, and <code>PUT /teacher/sessions/{session_id}/review-status</code> are documented. 검토 큐 visible 회귀와 교수자 전용 review grid 영역도 테스트로 고정했다.</td></tr>
|
<tr><td>Teacher review status</td><td><code>pytest app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_learner_dashboard.py -q</code> / <code>npm run typecheck</code> / <code>npm run build</code> / <code>teacher.spec.ts</code></td><td>12 backend passed + 4 E2E passed; <code>app.session_review_status</code>, teacher dashboard <code>review_status/review_note/reviewed_at</code>, worksheet <code>worksheet_status/worksheet_note/worksheet_reviewed_at</code>, and <code>PUT /teacher/sessions/{session_id}/review-status</code> are documented. 검토 큐 visible 회귀와 교수자 전용 review grid 영역도 테스트로 고정했다.</td></tr>
|
||||||
<tr><td>Persona authoring CRUD</td><td><code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py -q</code></td><td>33 passed; teacher/admin draft create/read/update/review submit, approved persona revision clone, code-family archive delete, learner blocked, approved-only catalog maintained, RAG draft generation prompt bundle id/version/hash pinned in metadata/provenance, seed/version runner dry-run manifest and apply pool lifecycle fixed.</td></tr>
|
<tr><td>Persona authoring CRUD</td><td><code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py -q</code></td><td>33 passed; teacher/admin draft create/read/update/review submit, approved persona revision clone, code-family archive delete, learner blocked, approved-only catalog maintained, RAG draft generation prompt bundle id/version/hash pinned in metadata/provenance, seed/version runner dry-run manifest and apply pool lifecycle fixed.</td></tr>
|
||||||
<tr><td>Repo source pack sync</td><td><code>python -B -m pytest -p no:cacheprovider app/test_live_coach_sources.py -q</code> / <code>py -3.11 scripts\sync-persona-sources.py --help</code></td><td>4 passed; dry-run reports next version without writes, apply bumps changed hash to latest+1, identical hash skips indexing, CLI exposes DB-backed dry-run/apply modes.</td></tr>
|
<tr><td>Repo source pack sync</td><td><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_live_coach_sources.py -q</code> / <code>py -3.11 -X utf8 scripts\sync-persona-sources.py --help</code></td><td>9 passed; dry-run reports next version without writes, apply bumps changed hash to latest+1, identical hash skips indexing, CLI exposes DB-backed dry-run/apply modes, and admin/CLI sync boundaries refresh the process-local live-coach source pack cache before comparing repo files. File-change regression proves stale cached content_hash becomes a new hash and next document version when refreshed.</td></tr>
|
||||||
<tr><td>P1 cost telemetry</td><td><code>app.test_session_turn_persistence.SessionTurnPersistenceTest</code></td><td>Passed; generate/stream client AI turns store provider/model/tokens/cost</td></tr>
|
<tr><td>P1 cost telemetry</td><td><code>app.test_session_turn_persistence.SessionTurnPersistenceTest</code></td><td>Passed; generate/stream client AI turns store provider/model/tokens/cost</td></tr>
|
||||||
<tr><td>Engine stream contract</td><td><code>node scripts/check-engine-gateway-contract.mjs --json</code> / <code>python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py -q</code> / <code>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</code></td><td>Node conformance runner OK, gateway contract 19 passed, L1 focused 50 passed; <code>EngineClient.stream_packets()</code> owns raw SSE decode, <code>EngineGatewaySseLineDecoder</code> parses token/done/error from the shared contract, keeps legacy token text compatibility, treats provider <code>[DONE]</code> as an ignored compatibility line rather than completion, rejects unknown gateway events, validates schema-backed <code>engine_gateway_contract.v1.json</code>, and gateway error event does not create saved turns.</td></tr>
|
<tr><td>Engine stream contract</td><td><code>node scripts/check-engine-gateway-contract.mjs --json</code> / <code>python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py -q</code> / <code>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</code></td><td>Node conformance runner OK, gateway contract 19 passed, L1 focused 50 passed; <code>EngineClient.stream_packets()</code> owns raw SSE decode, <code>EngineGatewaySseLineDecoder</code> parses token/done/error from the shared contract, keeps legacy token text compatibility, treats provider <code>[DONE]</code> as an ignored compatibility line rather than completion, rejects unknown gateway events, validates schema-backed <code>engine_gateway_contract.v1.json</code>, and gateway error event does not create saved turns.</td></tr>
|
||||||
<tr><td>Engine session reuse</td><td><code>engine_gateway.test_gateway_model</code></td><td>19 tests OK; shared engine contract, JSON Schema + golden fixture validation, Node.js artifact conformance runner, <code>GenerateResponse</code> response validation, SSE frames/decoder, live session_id reuse and ephemeral close fixed</td></tr>
|
<tr><td>Engine session reuse</td><td><code>engine_gateway.test_gateway_model</code></td><td>19 tests OK; shared engine contract, JSON Schema + golden fixture validation, Node.js artifact conformance runner, <code>GenerateResponse</code> response validation, SSE frames/decoder, live session_id reuse and ephemeral close fixed</td></tr>
|
||||||
<tr><td>P1/H4 masking gate</td><td><code>app/test_pii_masking_eval.py app/test_orchestrator_masking.py app/test_evaluation_persistence.py app/test_session_turn_persistence.py</code></td><td>43 passed; phone/email/RRN 및 한국어 NAME/ORG raw 값이 generate/stream/evaluator payload와 client <code>text_masked</code>에 남지 않음. Synthetic ko fixture 15/15 pass, 자연 발화형 이름 라벨·자기소개와 negative control 포함, input/report schema validation, summary-only report(<code>evidence_text_included=false</code>), entity recall 1.0, forbidden substring removal 1.0, unexpected entity violations 0.</td></tr>
|
<tr><td>P1/H4 masking gate</td><td><code>app/test_pii_masking_eval.py app/test_orchestrator_masking.py app/test_evaluation_persistence.py app/test_session_turn_persistence.py</code></td><td>47 passed; phone/email/RRN 및 한국어 NAME/ORG raw 값이 generate/stream/evaluator payload와 client <code>text_masked</code>에 남지 않음. Optional ko recognizer fake span은 <code>[NAME]</code>/<code>[ORG]</code>로 마스킹되고 같은 문장의 phone은 후단 regex가 처리하며, adapter 실패 시에도 regex fallback이 유지된다. Synthetic ko fixture 15/15 pass, 자연 발화형 이름 라벨·자기소개와 negative control 포함, input/report schema validation, summary-only report(<code>evidence_text_included=false</code>), entity recall 1.0, forbidden substring removal 1.0, unexpected entity violations 0.</td></tr>
|
||||||
<tr><td>P2a RBAC/audit/visibility</td><td><code>app.test_rbac_idor</code></td><td>8 tests OK; other learner 403, read_session audit, evaluator-only hidden, teacher session review read allowed while learner worksheet write remains 403</td></tr>
|
<tr><td>P2a RBAC/audit/visibility</td><td><code>app.test_rbac_idor</code></td><td>8 tests OK; other learner 403, read_session audit, evaluator-only hidden, teacher session review read allowed while learner worksheet write remains 403</td></tr>
|
||||||
<tr><td>Admin access delegation</td><td><code>python -m pytest app/test_auth_providers.py app/test_admin_ops.py -q</code> / <code>npm run typecheck</code> / <code>npm run check:api-types</code></td><td>32 backend passed; <code>admin_access</code> and <code>super_admin</code> are exposed through auth/admin DTOs. Super admins can grant/revoke admin page access; configured super admins stay protected. Frontend role switching uses <code>canAccessRole</code>.</td></tr>
|
<tr><td>Admin access delegation</td><td><code>python -m pytest app/test_auth_providers.py app/test_admin_ops.py -q</code> / <code>npm run typecheck</code> / <code>npm run check:api-types</code></td><td>32 backend passed; <code>admin_access</code> and <code>super_admin</code> are exposed through auth/admin DTOs. Super admins can grant/revoke admin page access; configured super admins stay protected. Frontend role switching uses <code>canAccessRole</code>.</td></tr>
|
||||||
<tr><td>Admin usage persistence</td><td><code>python -m pytest app/test_admin_ops.py app/test_runtime_policy.py -q</code> / authenticated local public-API smoke</td><td>26 passed; <code>/admin/usage</code> returns 200 with <code>source=database</code>, <code>durable=true</code>. Cloudflare blocked raw Python public smoke with 1010, so app-level HTTP was verified against <code>127.0.0.1:8001</code> using the same prod process.</td></tr>
|
<tr><td>Admin usage persistence</td><td><code>python -m pytest app/test_admin_ops.py app/test_runtime_policy.py -q</code> / authenticated local public-API smoke</td><td>26 passed; <code>/admin/usage</code> returns 200 with <code>source=database</code>, <code>durable=true</code>. Cloudflare blocked raw Python public smoke with 1010, so app-level HTTP was verified against <code>127.0.0.1:8001</code> using the same prod process.</td></tr>
|
||||||
|
|
|
||||||
|
|
@ -45,13 +45,13 @@
|
||||||
|
|
||||||
## 2. 갭 로드맵 — 심각도 우선순위
|
## 2. 갭 로드맵 — 심각도 우선순위
|
||||||
|
|
||||||
심각도 분류: **critical 3 / high 4 / medium+ 6**. C1의 "구조 전무"와 저장형 제출물 부재는 2차로 해소됐고, 3차에서는 임상팀 루브릭을 외부 JSON으로 받을 schema/loader/validation scaffold까지 추가했다. 임상 루브릭 콘텐츠·AI 채점·교수자 검수는 계속 추적한다. `✓`는 작성자가 코드 grep으로 직접 재확인한 항목, `(분석)`은 분석 결과로 착수 전 코드 1차 재확인 권고.
|
심각도 분류: **critical 3 / high 4 / medium+ 6**. C1의 "구조 전무"와 저장형 제출물 부재는 2차로 해소됐고, 3차에서는 임상팀 루브릭을 외부 JSON으로 받을 schema/loader/validation scaffold까지 추가했다. 4차에서는 교수자 수동 검수 상태 저장/표시 골격까지 연결했다. 임상 루브릭 콘텐츠·AI 채점·검수 정책 고도화는 계속 추적한다. `✓`는 작성자가 코드 grep으로 직접 재확인한 항목, `(분석)`은 분석 결과로 착수 전 코드 1차 재확인 권고.
|
||||||
|
|
||||||
### Critical (3)
|
### Critical (3)
|
||||||
|
|
||||||
| ID | 갭 | 현재상태 | 권고 | 근거 |
|
| ID | 갭 | 현재상태 | 권고 | 근거 |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| **C1** | 사례개념화·치료계획 산출물 저장형 구조 3차 구현·루브릭 외부화 scaffold 완료·채점/검수 잔여 | `SessionReviewResponse.caseWorksheet`가 축어록 근거 기반 초안을 제공하고, 리뷰 화면에서 학습자가 편집한 워크시트를 `PUT /sessions/{id}/review/worksheet`로 `app.case_worksheet`에 저장한다. 이후 `GET /review`는 `saved_by_learner` 저장본을 자동 초안보다 우선 반환한다. 3차에서는 `data/rubrics/case-worksheet-rubric.json`, Draft 2020-12 schema, `app.services.case_worksheet_rubric`, `scripts/check-case-worksheet-rubric.py`를 추가해 임상팀 확정 루브릭을 코드 수정 없이 받을 scaffold를 만들었다. 현재 rubric은 `scaffold_only`/`scoring_enabled=false`이며 서버 생성 워크시트 5개 section/28개 item key와 일치하는지만 검증한다. 템플릿 key source는 `CASE_WORKSHEET_SECTION_SPECS`와 `case_worksheet_template_item_keys()`로 명시해 validator/CLI/test가 더미 턴 없이 같은 생성 spec을 참조한다. CCD는 숨은 정답키로 남고 저장본에 점수로 노출하지 않는다. | 임상팀 확정 루브릭 콘텐츠(level anchor, 가중치, 컷오프, 예시 답안), AI 채점 적용·calibration, 교수자 승인/반려/수정요청 검수 플로우. | doc1·doc4·doc5 |
|
| **C1** | 사례개념화·치료계획 산출물 저장형 구조 4차 구현·루브릭 외부화 scaffold·수동 교수자 검수 골격 완료·채점/정책 잔여 | `SessionReviewResponse.caseWorksheet`가 축어록 근거 기반 초안을 제공하고, 리뷰 화면에서 학습자가 편집한 워크시트를 `PUT /sessions/{id}/review/worksheet`로 `app.case_worksheet`에 저장한다. 이후 `GET /review`는 `saved_by_learner` 저장본을 자동 초안보다 우선 반환한다. 3차에서는 `data/rubrics/case-worksheet-rubric.json`, Draft 2020-12 schema, `app.services.case_worksheet_rubric`, `scripts/check-case-worksheet-rubric.py`를 추가해 임상팀 확정 루브릭을 코드 수정 없이 받을 scaffold를 만들었다. 현재 rubric은 `scaffold_only`/`scoring_enabled=false`이며 서버 생성 워크시트 5개 section/28개 item key와 일치하는지만 검증한다. 템플릿 key source는 `CASE_WORKSHEET_SECTION_SPECS`와 `case_worksheet_template_item_keys()`로 명시해 validator/CLI/test가 더미 턴 없이 같은 생성 spec을 참조한다. 4차에서는 기존 `app.session_review_status`에 `worksheet_status`/`worksheet_note`/`worksheet_reviewed_at`을 붙이고, `PUT /teacher/sessions/{id}/review-status`와 교수자 `SessionReview` 카드에서 승인·수정요청·반려 수동 판정을 저장/표시한다. CCD는 숨은 정답키로 남고 저장본에 점수로 노출하지 않는다. | 임상팀 확정 루브릭 콘텐츠(level anchor, 가중치, 컷오프, 예시 답안), AI 채점 적용·calibration, 승인 후 잠금·재제출 정책, 재귀학습 데이터셋 approved 연계. | doc1·doc4·doc5 |
|
||||||
| **C2** | 위기개입 프로토콜·생명유지서약·에스컬레이션 1차 배선 완료·임상 고도화 필요 | 실제 자해·자살 신호는 LLM/엔진 호출 전 중단하고 109 리소스와 `conversation_stopped`를 REST/SSE/voice 응답에 싣는다. `crisis.risk_level`은 상태머신 `ideation_observed`로 전달하고, escalate 시 `app.safety_events` detail 적재 및 교수자 대시보드 안전 알림 큐로 연결한다. | 남은 것은 임상팀 콘텐츠: 비밀보장 예외고지→단계적 탐색→생명유지서약 스크립트, 실시간 push/메일 알림 정책, 위기탐색 누락 시 회기리뷰 감점 루브릭. | doc1·doc5(핵심 시나리오)·doc4(IRB 전제) |
|
| **C2** | 위기개입 프로토콜·생명유지서약·에스컬레이션 1차 배선 완료·임상 고도화 필요 | 실제 자해·자살 신호는 LLM/엔진 호출 전 중단하고 109 리소스와 `conversation_stopped`를 REST/SSE/voice 응답에 싣는다. `crisis.risk_level`은 상태머신 `ideation_observed`로 전달하고, escalate 시 `app.safety_events` detail 적재 및 교수자 대시보드 안전 알림 큐로 연결한다. | 남은 것은 임상팀 콘텐츠: 비밀보장 예외고지→단계적 탐색→생명유지서약 스크립트, 실시간 push/메일 알림 정책, 위기탐색 누락 시 회기리뷰 감점 루브릭. | doc1·doc5(핵심 시나리오)·doc4(IRB 전제) |
|
||||||
| **C3** | 이론모드 2차 배선·학습자 명시 선택 UI 완료·CBT 콘텐츠 잔여 | `TurnContext`/`prepare_turn`/sessions/voice/evaluator에 `theory_mode`가 전달되고, `build_turn_messages`도 인간중심·CBT·통합 이론 프레이밍을 엔진 메시지에 넣는다. 프론트는 `persona.theory_target` 기준 기본값을 잡되, 세션 시작 전 `humanistic`/`cbt`/`integrative` segmented control로 학습자가 명시 선택하고 `POST /sessions`의 `theory_mode`로 전송한다. | 임상팀이 확정한 CBT 단계 프롬프트 체인, 이론부합 채점 루브릭. 이번 UI는 임상 문안·루브릭·백엔드 enum을 확장하지 않았다. | doc4(humanistic+CBT 필수)·doc2/doc5 |
|
| **C3** | 이론모드 2차 배선·학습자 명시 선택 UI 완료·CBT 콘텐츠 잔여 | `TurnContext`/`prepare_turn`/sessions/voice/evaluator에 `theory_mode`가 전달되고, `build_turn_messages`도 인간중심·CBT·통합 이론 프레이밍을 엔진 메시지에 넣는다. 프론트는 `persona.theory_target` 기준 기본값을 잡되, 세션 시작 전 `humanistic`/`cbt`/`integrative` segmented control로 학습자가 명시 선택하고 `POST /sessions`의 `theory_mode`로 전송한다. | 임상팀이 확정한 CBT 단계 프롬프트 체인, 이론부합 채점 루브릭. 이번 UI는 임상 문안·루브릭·백엔드 enum을 확장하지 않았다. | doc4(humanistic+CBT 필수)·doc2/doc5 |
|
||||||
|
|
||||||
|
|
@ -60,20 +60,20 @@
|
||||||
| ID | 갭 | 현재상태 | 권고 | 근거 |
|
| ID | 갭 | 현재상태 | 권고 | 근거 |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| **H1** | 계약 평가 KPI(자기효능감·기술숙련도·수련만족도 사전사후) 수집·입력·CSV/report 계산 1차 | `app.learner_prepost_measure`, 학습자 본인용 `GET/PUT /users/me/prepost-measures`, `SessionReview`의 파일럿 증거 원장 카드가 3척도 pre/post 1~5 aggregate evidence를 저장·조회한다. `app.services.phase3_kpi_export`와 `scripts/export-phase3-kpi.py`는 원장 row를 Phase 3 evidence root의 `02-measures/prepost_measures.csv`와 `02-measures/kpi_report.json` scaffold로 산출한다. participant id는 가명화하고, 3척도 paired normalized mean pre/post/delta, complete/missing pair를 계산한다. | 공식 문항 확정, 실험/통제군 배정, 추이 시각화, 통계검정 종류/alpha/결측 처리, 실제 20명 evidence와 steward/legal/IAA 검수. 현재 API/UI/export는 공식 효과성·성적·수료 판정이 아니라 파일럿 evidence 계산이다. (κ/ICC·환각률은 doc4 미명시 → 평가설계 확정.) | doc4(20명 실험/통제군·단회기 50분·3척도 pre-post) |
|
| **H1** | 계약 평가 KPI(자기효능감·기술숙련도·수련만족도 사전사후) 수집·입력·CSV/report 계산 1차 | `app.learner_prepost_measure`, 학습자 본인용 `GET/PUT /users/me/prepost-measures`, `SessionReview`의 파일럿 증거 원장 카드가 3척도 pre/post 1~5 aggregate evidence를 저장·조회한다. `app.services.phase3_kpi_export`와 `scripts/export-phase3-kpi.py`는 원장 row를 Phase 3 evidence root의 `02-measures/prepost_measures.csv`와 `02-measures/kpi_report.json` scaffold로 산출한다. participant id는 가명화하고, 3척도 paired normalized mean pre/post/delta, complete/missing pair를 계산한다. | 공식 문항 확정, 실험/통제군 배정, 추이 시각화, 통계검정 종류/alpha/결측 처리, 실제 20명 evidence와 steward/legal/IAA 검수. 현재 API/UI/export는 공식 효과성·성적·수료 판정이 아니라 파일럿 evidence 계산이다. (κ/ICC·환각률은 doc4 미명시 → 평가설계 확정.) | doc4(20명 실험/통제군·단회기 50분·3척도 pre-post) |
|
||||||
| **H2** | 턴별 fast-loop + 라이브 코칭 1차 가동·학습자 리뷰 2열 UI 1차 완료·골든셋 잔여 | `make_eval_hook`이 submit/voice 생성 경로에 주입되고, stream은 `_evaluate_stream_turn`으로 fast-loop 평가를 붙인다. 결과는 `feedback_scores`, `alternative_utterance` 등 정규화 테이블에 적재·hydrate된다. 추가로 `app/services/live_coach.py`, `POST/GET /sessions/{id}/live-coach`, `POST /kb/live-coach/source-packs/sync`, `app.live_coach_events`, `data/kb/live_coaching_workbook_0615.json`, `data/kb/live_coaching_sources/*.json`을 연결해 워크북·DSM·공식 지침 요약 기반 코칭 아바타 말풍선·근거 모달·발화별 이력 오버레이와 RAG 증분 색인을 제공한다. `app.services.source_pack_sync`가 repo source pack의 active `content_hash`를 비교하고 변경 시 document version을 최신+1로 올린다. 학습자 `SessionReview` 데스크톱은 좌측 축어록 타임라인, 우측 요약·감정·흐름·루브릭·강점·개선점·pre/post·워크시트·피드백 작업열의 2열 구조로 재배치했다. | 원천 축어록 few-shot 골든셋 적재, 임상팀 확정 루브릭과 source pack 임상 검수 상태 운영정책 보강. | doc2·doc5(골드 포맷) |
|
| **H2** | 턴별 fast-loop + 라이브 코칭 1차 가동·학습자 리뷰 2열 UI 1차 완료·골든셋 잔여 | `make_eval_hook`이 submit/voice 생성 경로에 주입되고, stream은 `_evaluate_stream_turn`으로 fast-loop 평가를 붙인다. 결과는 `feedback_scores`, `alternative_utterance` 등 정규화 테이블에 적재·hydrate된다. 추가로 `app/services/live_coach.py`, `POST/GET /sessions/{id}/live-coach`, `POST /kb/live-coach/source-packs/sync`, `app.live_coach_events`, `data/kb/live_coaching_workbook_0615.json`, `data/kb/live_coaching_sources/*.json`을 연결해 워크북·DSM·공식 지침 요약 기반 코칭 아바타 말풍선·근거 모달·발화별 이력 오버레이와 RAG 증분 색인을 제공한다. `app.services.source_pack_sync`가 repo source pack의 active `content_hash`를 비교하고 변경 시 document version을 최신+1로 올린다. live turn은 프로세스 로컬 source pack snapshot을 재사용하지만, 관리자 sync/CLI는 `refresh=True`로 캐시를 비운 뒤 repo 파일을 다시 읽어 stale `content_hash` 비교를 막는다. 학습자 `SessionReview` 데스크톱은 좌측 축어록 타임라인, 우측 요약·감정·흐름·루브릭·강점·개선점·pre/post·워크시트·피드백 작업열의 2열 구조로 재배치했다. | 원천 축어록 few-shot 골든셋 적재, 임상팀 확정 루브릭과 source pack 임상 검수 상태 운영정책 보강. | doc2·doc5(골드 포맷) |
|
||||||
| **H3** | 임상팀 콘텐츠 입력 경로(페르소나 저작 CRUD) 2차 구현·원문 격리 정책 1차·항목형 목록 저작/프롬프트 검토 UI 완료·임상 검수 잔여 | draft 생성·조회·편집·검수요청 API와 교수 콘솔 JSON 초안 패널은 연결됐다. `persona_repository.py`는 in-code `SEED_PERSONAS`(P1~P3)와 `data/personas/P4.json`~`P7.json`을 `PersonaCard`로 합쳐 `materialize_seed_personas()`와 seed fallback catalog에 포함한다. `scripts/materialize-persona-seeds.py`는 같은 seed manifest를 dry-run 기본으로 보고하고, `--apply`에서만 DB pool을 초기화한 뒤 기존 idempotent DB materializer를 호출한다. `scripts/sync-persona-sources.py`는 DB-backed dry-run/apply runner로 repo-managed source pack의 `content_hash`/document version을 비교한다. RAG 기반 draft 생성은 source/chunk evidence와 함께 `persona-draft-rag@2026-06-28.1` prompt bundle id/version/hash를 engine metadata 및 draft `source_provenance`에 남긴다. `POST /personas/sources`는 raw 원문 hash-only 증거를 `kb.raw_source_artifact`에 따로 기록하고, sanitized 파생본만 evaluator-only RAG chunk로 색인한다. `rag.index_document()`는 `sensitivity=3` 또는 raw marker chunk를 DB 접근 전에 차단한다. `app/persona_read_model.py`는 catalog/review/draft/source/evidence DTO와 mapper를 route에서 분리해 OpenAPI schema 이름을 유지한다. PersonaStudio는 자동사고, 회기 시나리오, 말투 filler/verbal tic/nonverbal cue, 역린·금기 응답·금기어를 행 추가/삭제 UI로 편집하고, 저장 직전 빈 항목을 제거하되 기존 배열 schema를 유지한다. 프롬프트 탭은 raw JSON textarea 대신 L1 카드·인적 범주·임상 배경·말투·수치 파라미터·역린·회기 시나리오·추가 계약 섹션으로 같은 draft 데이터를 검토하게 한다. | 루브릭·이론 콘텐츠 외부화, P4~P7 포함 임상팀 최종 검수/서면 evidence 확보, 암호화 blob/vault 기반 원문 실저장. | doc3(R&R)·doc4(페르소나=전문가 산출물) |
|
| **H3** | 임상팀 콘텐츠 입력 경로(페르소나 저작 CRUD) 2차 구현·원문 격리 정책 1차·항목형 목록 저작/프롬프트 검토 UI 완료·임상 검수 잔여 | draft 생성·조회·편집·검수요청 API와 교수 콘솔 JSON 초안 패널은 연결됐다. `persona_repository.py`는 in-code `SEED_PERSONAS`(P1~P3)와 `data/personas/P4.json`~`P7.json`을 `PersonaCard`로 합쳐 `materialize_seed_personas()`와 seed fallback catalog에 포함한다. `scripts/materialize-persona-seeds.py`는 같은 seed manifest를 dry-run 기본으로 보고하고, `--apply`에서만 DB pool을 초기화한 뒤 기존 idempotent DB materializer를 호출한다. `scripts/sync-persona-sources.py`는 DB-backed dry-run/apply runner로 repo-managed source pack의 `content_hash`/document version을 비교한다. RAG 기반 draft 생성은 source/chunk evidence와 함께 `persona-draft-rag@2026-06-28.1` prompt bundle id/version/hash를 engine metadata 및 draft `source_provenance`에 남긴다. `POST /personas/sources`는 raw 원문 hash-only 증거를 `kb.raw_source_artifact`에 따로 기록하고, sanitized 파생본만 evaluator-only RAG chunk로 색인한다. `rag.index_document()`는 `sensitivity=3` 또는 raw marker chunk를 DB 접근 전에 차단한다. `app/persona_read_model.py`는 catalog/review/draft/source/evidence DTO와 mapper를 route에서 분리해 OpenAPI schema 이름을 유지한다. PersonaStudio는 자동사고, 회기 시나리오, 말투 filler/verbal tic/nonverbal cue, 역린·금기 응답·금기어를 행 추가/삭제 UI로 편집하고, 저장 직전 빈 항목을 제거하되 기존 배열 schema를 유지한다. 프롬프트 탭은 raw JSON textarea 대신 L1 카드·인적 범주·임상 배경·말투·수치 파라미터·역린·회기 시나리오·추가 계약 섹션으로 같은 draft 데이터를 검토하게 한다. | 루브릭·이론 콘텐츠 외부화, P4~P7 포함 임상팀 최종 검수/서면 evidence 확보, 암호화 blob/vault 기반 원문 실저장. | doc3(R&R)·doc4(페르소나=전문가 산출물) |
|
||||||
| **H4** | PII 마스킹 한국어 이름/기관 로컬 1차 + 15-case fixture/schema 평가 harness + 온보딩·동의 게이트 잔여 | Presidio `language='en'` 고정이라 한국어 이름/기관 정밀 NER 한계는 남아 있지만, 정규식 폴백에 한국어 날짜·금액·행정구역 주소와 함께 이름/성명 라벨, 성씨+이름+조사/호칭, 대학교·학과·병원·센터 등 기관 suffix 기반 로컬 휴리스틱 마스킹을 추가했다. 66차에서는 `제 이름은 김서연입니다`, `보호자 이름은 박민수입니다`, `저는 최하늘입니다` 같은 자연 발화형 이름 라벨·자기소개 패턴을 추가하고 `이름은 중요하지 않다` negative control로 오탐을 막았다. Presidio가 설치돼도 한국어 누락을 막기 위해 fallback을 후단에 한 번 더 태운다. 상담 생성(generate/stream), fast evaluator prompt, client turn `text_masked`에서 한국어 NAME/ORG raw 값이 남지 않도록 회귀화했다. `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 범위에서 entity recall·forbidden substring removal·unexpected entity violation으로 평가한다. `data/privacy/pii-masking-eval-input.schema.json`과 `data/privacy/pii-masking-eval-report.schema.json`은 source/category/severity metadata와 summary-only `technical_dry_run` 리포트 계약을 고정하며, 기본 CLI JSON은 `masked_text`/`forbidden_remaining` 원문 증거를 제외한다. `소속`/`안내`/`이름` NAME 오탐도 stopword로 보정했다. 외부 LLM 호출은 상담 생성(generate/stream)·fast/deep 평가 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency만 적재하도록 연결했고, prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log` 3행 증가를 확인했다. `app_user`에 이름·소속·학과·학년/직위·연락처·주소/수령지·닉네임·자기소개·아바타 URL·약관/개인정보 동의 버전 필드를 추가했고, 로그인 직후 `/onboarding` 완료 전에는 역할 홈과 learner 회기 시작을 막는다. 아바타 이미지는 `/users/me/avatar`에서 MIME/시그니처/3MB 제한 후 파일 저장소에 두고 URL만 보관한다. 온보딩 저장 시 learner `consent_at`도 함께 세팅하며 auth E2E에서 신규 계정 온보딩→아바타 업로드→학습자 홈 이동을 검증했다. | 모델 기반 ko NER 정밀화, 실제 운영 말뭉치 기반 오탐/미탐 평가, 미성년/guardian 및 법무 검토가 필요한 최종 서명 동의서·개인정보 처리방침·약관 evidence 확보. 공개 Google OAuth 실제 `/turn` proof는 별도 운영 게이트. | doc1/2/5(실명·날짜·미성년·자살시도 다수)·doc4(IRB·개인정보) |
|
| **H4** | PII 마스킹 한국어 로컬 휴리스틱 + optional ko recognizer adapter + 15-case fixture/schema 평가 harness + 온보딩·동의 게이트 잔여 | Presidio `language='en'` 고정 한계를 보완하기 위해 정규식 폴백에 한국어 날짜·금액·행정구역 주소와 함께 이름/성명 라벨, 성씨+이름+조사/호칭, 대학교·학과·병원·센터 등 기관 suffix 기반 로컬 휴리스틱 마스킹을 추가했다. 66차에서는 `제 이름은 김서연입니다`, `보호자 이름은 박민수입니다`, `저는 최하늘입니다` 같은 자연 발화형 이름 라벨·자기소개 패턴을 추가하고 `이름은 중요하지 않다` negative control로 오탐을 막았다. Presidio가 설치돼도 한국어 누락을 막기 위해 fallback을 후단에 한 번 더 태운다. 70차에서는 `guardrail.mask_pii()` 내부에 선택형 한국어 PII recognizer adapter 경계를 추가했다. adapter는 import-time hard dependency가 아니며 명시 등록 전에는 비활성이고, 실패해도 기존 regex fallback이 마지막 안전망으로 유지된다. fake adapter 테스트는 regex가 못 잡는 별명/기관 span을 `[NAME]`/`[ORG]`로 마스킹하고 같은 문장의 전화번호는 후단 regex가 `[PHONE]`으로 처리하는지, adapter 실패 시에도 fallback이 유지되는지 검증한다. 상담 생성(generate/stream), fast evaluator prompt, client turn `text_masked`에서 한국어 NAME/ORG raw 값이 남지 않도록 회귀화했다. `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 범위에서 entity recall·forbidden substring removal·unexpected entity violation으로 평가한다. `data/privacy/pii-masking-eval-input.schema.json`과 `data/privacy/pii-masking-eval-report.schema.json`은 source/category/severity metadata와 summary-only `technical_dry_run` 리포트 계약을 고정하며, 기본 CLI JSON은 `masked_text`/`forbidden_remaining` 원문 증거를 제외한다. `소속`/`안내`/`이름` NAME 오탐도 stopword로 보정했다. 외부 LLM 호출은 상담 생성(generate/stream)·fast/deep 평가 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency만 적재하도록 연결했고, prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log` 3행 증가를 확인했다. `app_user`에 이름·소속·학과·학년/직위·연락처·주소/수령지·닉네임·자기소개·아바타 URL·약관/개인정보 동의 버전 필드를 추가했고, 로그인 직후 `/onboarding` 완료 전에는 역할 홈과 learner 회기 시작을 막는다. 아바타 이미지는 `/users/me/avatar`에서 MIME/시그니처/3MB 제한 후 파일 저장소에 두고 URL만 보관한다. 온보딩 저장 시 learner `consent_at`도 함께 세팅하며 auth E2E에서 신규 계정 온보딩→아바타 업로드→학습자 홈 이동을 검증했다. | 실제 ko recognizer 모델/provider 선정, 운영 말뭉치 기반 오탐/미탐 평가, 미성년/guardian 및 법무 검토가 필요한 최종 서명 동의서·개인정보 처리방침·약관 evidence 확보. 공개 Google OAuth 실제 `/turn` proof는 별도 운영 게이트. | doc1/2/5(실명·날짜·미성년·자살시도 다수)·doc4(IRB·개인정보) |
|
||||||
|
|
||||||
### Medium+ (6) — M1~M3, X1~X2, L1
|
### Medium+ (6) — M1~M3, X1~X2, L1
|
||||||
|
|
||||||
| ID | 갭 | 현재상태 | 권고 |
|
| ID | 갭 | 현재상태 | 권고 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **M1** | 비언어/준언어 임상 이벤트 캡처·태깅 4차 진행 | 1차에서 `audio_ref`/`silence_ms`/`speech_rate`/`barge_in`을 learner voice turn에 저장하고 리뷰 `nonverbal` 칩으로 파생했다. 2차에서는 `app.turns.provider_events JSONB`와 `TurnRecord.provider_events`를 추가해 WebSocket control/STT provider 이벤트를 allowlist·size limit 후 보존한다. 3차에서는 저장 전 sanitizer에서 내부 taxonomy `event_type`/`category`를 붙인다. 4차에서는 인증된 회기 리뷰 API가 `sigh`/`cry`/`laugh`/`breath`, prosody, background noise 계열만 한글 label/detail 칩으로 파생 노출한다. raw transcript/text payload, provider/source/raw type, 공개 공유 카드 노출은 제외한다. | 실제 provider 기반 한숨·울음·억양 감지 연결, 장시간 마이크/WSS 실측, 리뷰 칩을 역량 지표로 해석할지에 대한 정책. |
|
| **M1** | 비언어/준언어 임상 이벤트 캡처·태깅 4차 진행 | 1차에서 `audio_ref`/`silence_ms`/`speech_rate`/`barge_in`을 learner voice turn에 저장하고 리뷰 `nonverbal` 칩으로 파생했다. 2차에서는 `app.turns.provider_events JSONB`와 `TurnRecord.provider_events`를 추가해 WebSocket control/STT provider 이벤트를 allowlist·size limit 후 보존한다. 3차에서는 저장 전 sanitizer에서 내부 taxonomy `event_type`/`category`를 붙인다. 4차에서는 인증된 회기 리뷰 API가 `sigh`/`cry`/`laugh`/`breath`, prosody, background noise 계열만 한글 label/detail 칩으로 파생 노출한다. raw transcript/text payload, provider/source/raw type, 공개 공유 카드 노출은 제외한다. | 실제 provider 기반 한숨·울음·억양 감지 연결, 장시간 마이크/WSS 실측, 리뷰 칩을 역량 지표로 해석할지에 대한 정책. |
|
||||||
| **M2** | 다회기 종단 케이스 아크·교차회기 사례개념화 6차 구동 | `(persona_id, learner_id)` 안정 `case_profile` upsert, 원자적 `session_no`, voice/REST/SSE recall cache 주입을 연결했다. 세션 종료 시 마스킹 축어록 기반 fallback `session_summary.digest`, `case_profile.case_digest`, `rapport_trajectory`, `alliance_level`을 갱신하고, 다음 회기 seed recall은 `case_digest`·직전 `session_summary`·client-visible non-contradicted `pinned_fact`를 함께 조립한다. 3차에서는 마스킹된 client-visible 발화에서 `[NAME]`/`[ORG]` identity와 명시적 상담 약속만 보수적으로 `pinned_fact`에 upsert한다. 4차에서는 pinned fact 삽입 또는 값 변경 시 `pinned_fact_history`에 append-only 이력을 남긴다. 5차에서는 명시적 상담 약속 철회/부정만 기존 non-locked `agreement:counseling` fact를 `contradicted`로 격리하고 history reason `contradiction`을 남긴다. 6차에서는 세션 종료 저장 성공 뒤 마스킹된 client-visible 내담자 발화만 `app.turn_embedding`에 BGE-M3 dense/sparse로 idempotent 색인한다. 같은 값 재확인은 history를 늘리지 않고, `locked` fact는 건드리지 않는다. 관계갈등·위기·임상 추론은 자동 pinning/모순 처리에서 제외한다. | 관계·임상 fact 승격 기준, LLM digest 압축, 접수면접→다회기 연속성·자기개념 진화 실증. |
|
| **M2** | 다회기 종단 케이스 아크·교차회기 사례개념화 8차 구동 | `(persona_id, learner_id)` 안정 `case_profile` upsert, 원자적 `session_no`, voice/REST/SSE recall cache 주입을 연결했다. 세션 종료 시 마스킹 축어록 기반 fallback `session_summary.digest`, `case_profile.case_digest`, `rapport_trajectory`, `alliance_level`을 갱신하고, 다음 회기 seed recall은 `case_digest`·직전 `session_summary`·client-visible non-contradicted `pinned_fact`를 함께 조립한다. 3차에서는 마스킹된 client-visible 발화에서 `[NAME]`/`[ORG]` identity와 명시적 상담 약속만 보수적으로 `pinned_fact`에 upsert한다. 4차에서는 pinned fact 삽입 또는 값 변경 시 `pinned_fact_history`에 append-only 이력을 남긴다. 5차에서는 명시적 상담 약속 철회/부정만 기존 non-locked `agreement:counseling` fact를 `contradicted`로 격리하고 history reason `contradiction`을 남긴다. 6차에서는 세션 종료 저장 성공 뒤 마스킹된 client-visible 내담자 발화만 `app.turn_embedding`에 BGE-M3 dense/sparse로 idempotent 색인한다. 7차에서는 REST submit/SSE stream의 턴 컨텍스트 조립 경계를 `_prepare_turn_context()`로 묶고, DB seed recall과 pinned fact가 다음 턴 EngineMessage L2/L4에 raw 이름 마스킹 상태로 주입되는 route-level 회귀를 추가했다. 8차에서는 `SessionDigestInput`/`SessionDigestResult`/`SessionSummaryWrite`로 종료 digest 입력·fallback 결과·DB write 인자 경계를 명시해 future LLM worker가 raw text, evaluator-only turn, 평가 payload, CCD, deterministic carry를 압축 prompt에 우회 주입하지 못하게 했다. `digest_pending`은 CompressionJob 생성 여부를 알리는 비동기 압축 필요 신호로 유지한다. 같은 값 재확인은 history를 늘리지 않고, `locked` fact는 건드리지 않는다. 관계갈등·위기·임상 추론은 자동 pinning/모순 처리에서 제외한다. | 관계·임상 fact 승격 기준, LLM digest worker 실행/품질평가/재압축, 접수면접→다회기 연속성·자기개념 진화 실증. |
|
||||||
| **M3** | SSO claim 매핑·식별자 안정성 1차 완료·운영 IdP 감사 미연결 | Google/SAML/dev-login이 `AUTH_EMAIL_COHORT_MAP`·`AUTH_DOMAIN_COHORT_MAP` 및 SAML cohort claim을 `cohort_ids`로 전달하고, `app_user.external_id`는 provider subject(`google:`/`saml:`/`dev:`) 기반으로 저장한다. 운영 SAML 서명검증, 기관 claim schema/test tenant, deprovisioning audit은 아직 없다. | 한신 IdP 확정 후 SAML 서명검증, claim→role/cohort/institution_user_id 매핑 표 실연동, role변경/삭제 audit, deprovisioning evidence. |
|
| **M3** | SSO claim 매핑·식별자 안정성 1차 완료·운영 IdP 감사 미연결 | Google/SAML/dev-login이 `AUTH_EMAIL_COHORT_MAP`·`AUTH_DOMAIN_COHORT_MAP` 및 SAML cohort claim을 `cohort_ids`로 전달하고, `app_user.external_id`는 provider subject(`google:`/`saml:`/`dev:`) 기반으로 저장한다. 운영 SAML 서명검증, 기관 claim schema/test tenant, deprovisioning audit은 아직 없다. | 한신 IdP 확정 후 SAML 서명검증, claim→role/cohort/institution_user_id 매핑 표 실연동, role변경/삭제 audit, deprovisioning evidence. |
|
||||||
| **X1** | 재귀학습·데이터셋 export 파이프라인 1차 구현 | `scripts/export-recursive-dataset.py`와 `app.services.dataset_export`로 masked-text JSONL dry-run, PII scan, kappa/ICC 계산, approved export 게이트를 구현했다. 기본은 `technical_dry_run`이며 실제 승인 export·골든셋 승격은 데이터 steward/legal review와 IAA 통과가 필요하다. | 파일럿 evidence에서 reviewer disposition, steward/legal 승인, gold annotation 라운드 적재 후 `approved_for_recursive_learning_seed` 승격 검증. |
|
| **X1** | 재귀학습·데이터셋 export 파이프라인 1차 구현 | `scripts/export-recursive-dataset.py`와 `app.services.dataset_export`로 masked-text JSONL dry-run, PII scan, kappa/ICC 계산, approved export 게이트를 구현했다. 기본은 `technical_dry_run`이며 실제 승인 export·골든셋 승격은 데이터 steward/legal review와 IAA 통과가 필요하다. | 파일럿 evidence에서 reviewer disposition, steward/legal 승인, gold annotation 라운드 적재 후 `approved_for_recursive_learning_seed` 승격 검증. |
|
||||||
| **X2** | AI API 비용 관측·예산 경고·평가 저비용 라우팅·evaluator cache 관측·일별 비용 추이·모델별 비용 검증 리포트 2차 완료 | 턴별 provider/model/tokens/cost 저장 경로와 `GET /admin/usage`, 관리자 비용 대시보드를 연결했다. `ADMIN_USAGE_BUDGET_USD` 기준 예산 상태(ok/warn/exceeded)도 응답/UI에 표시한다. `EVALUATOR_FAST_MODEL`/`EVALUATOR_DEEP_MODEL` 설정 시 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달하고, 비워두면 기존 gateway default 라우팅을 유지한다. fast/deep evaluator structured 결과는 canonical request SHA-256 기반 인메모리 semantic cache로 재사용하며, 원문 prompt·completion은 저장하지 않고 성공 파싱 결과만 TTL/entry 제한 안에서 캐시한다. `/admin/usage`와 관리자 비용 카드가 cache enabled/entries/hits/misses/stores/evictions/requests/hit_rate와 일별 `daily_cost` 추이를 노출한다. `app.services.usage_report`와 `scripts/report-ai-usage.py`는 같은 usage JSON에서 provider/model별 cost share, token share, cost/turn, cost/1k tokens, metered coverage, budget/cache warning을 산출한다. DB 미가용 dev는 runtime store fallback, prod는 fail-closed다. | 자동 차단·한도 enforcement 정책. |
|
| **X2** | AI API 비용 관측·예산 경고·평가 저비용 라우팅·evaluator cache 관측·일별 비용 추이·모델별 비용 검증 리포트 2차 완료 | 턴별 provider/model/tokens/cost 저장 경로와 `GET /admin/usage`, 관리자 비용 대시보드를 연결했다. `ADMIN_USAGE_BUDGET_USD` 기준 예산 상태(ok/warn/exceeded)도 응답/UI에 표시한다. `EVALUATOR_FAST_MODEL`/`EVALUATOR_DEEP_MODEL` 설정 시 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달하고, 비워두면 기존 gateway default 라우팅을 유지한다. fast/deep evaluator structured 결과는 canonical request SHA-256 기반 인메모리 semantic cache로 재사용하며, 원문 prompt·completion은 저장하지 않고 성공 파싱 결과만 TTL/entry 제한 안에서 캐시한다. `/admin/usage`와 관리자 비용 카드가 cache enabled/entries/hits/misses/stores/evictions/requests/hit_rate와 일별 `daily_cost` 추이를 노출한다. `app.services.usage_report`와 `scripts/report-ai-usage.py`는 같은 usage JSON에서 provider/model별 cost share, token share, cost/turn, cost/1k tokens, metered coverage, budget/cache warning을 산출한다. DB 미가용 dev는 runtime store fallback, prod는 fail-closed다. | 자동 차단·한도 enforcement 정책. |
|
||||||
| **L1** | 기술스택 신청서-구현 불일치 및 단기일정 산출물 압박 | doc4 신청서 스택(Spring Boot 3/Node.js·TimescaleDB) vs 실제 FastAPI/Python 불일치, 20주 단기일정·9월 저작권 등재 압박. 소유자 결정으로 장기 교체 대상은 Node.js 우선, 현재 FastAPI 전면 재작성은 보류했다. 내부 전환 증거로 engine gateway 공유 계약, `EngineClient.stream_packets()` decode 경계, schema-backed golden fixture(`engine_gateway_contract.v1.json`/`engine_gateway_schema.v1.json`), Python import 없는 `scripts/check-engine-gateway-contract.mjs` Node.js conformance runner, 브라우저-facing 세션 read-model 분리(`app/session_read_model.py`), 페르소나 DTO/mapper 분리(`app/persona_read_model.py`)까지 고정했다. | 신청서/저작권 등재 문서에 FastAPI 유지 사유와 계약 우선 Node 전환 계획을 반영하는 외부 거버넌스 증거. 내부 후보였던 H3 항목형 목록 저작 UI와 프롬프트 미리보기 de-JSON은 10차에서 완료. |
|
| **L1** | 기술스택 신청서-구현 불일치 및 단기일정 산출물 압박 | doc4 신청서 스택(Spring Boot 3/Node.js·TimescaleDB) vs 실제 FastAPI/Python 불일치, 20주 단기일정·9월 저작권 등재 압박. 소유자 결정으로 장기 교체 대상은 Node.js 우선, 현재 FastAPI 전면 재작성은 보류했다. 내부 전환 증거로 engine gateway 공유 계약, `EngineClient.stream_packets()` decode 경계, schema-backed golden fixture(`engine_gateway_contract.v1.json`/`engine_gateway_schema.v1.json`), Python import 없는 `scripts/check-engine-gateway-contract.mjs` Node.js conformance runner, 브라우저-facing 세션 read-model 분리(`app/session_read_model.py`), 페르소나 DTO/mapper 분리(`app/persona_read_model.py`), stage 라벨/phase-key 정규화 SSOT(`app/stage_contract.py`)까지 고정했다. | 신청서/저작권 등재 문서에 FastAPI 유지 사유와 계약 우선 Node 전환 계획을 반영하는 외부 거버넌스 증거. 내부 후보였던 H3 항목형 목록 저작 UI와 프롬프트 미리보기 de-JSON은 10차에서 완료. |
|
||||||
|
|
||||||
> X2 근거: doc3 회의록이 'AI API 비용'을 운영 리스크로 명시.
|
> X2 근거: doc3 회의록이 'AI API 비용'을 운영 리스크로 명시.
|
||||||
|
|
||||||
|
|
@ -85,12 +85,12 @@
|
||||||
|
|
||||||
### A. 즉시 착수 가능 (내부 코드, 외부 합의 불요)
|
### A. 즉시 착수 가능 (내부 코드, 외부 합의 불요)
|
||||||
|
|
||||||
- **C1 3차 완료**: `caseWorksheet` 응답 구조, 리뷰 화면 편집 UI, `app.case_worksheet` 저장/재조회 경로와 외부 루브릭 schema/loader/validation scaffold. 워크시트 템플릿 key source는 `CASE_WORKSHEET_SECTION_SPECS`/`case_worksheet_template_item_keys()`로 명시했다. 후속은 임상팀 확정 루브릭 콘텐츠, AI 채점 보정, 교수자 검수.
|
- **C1 4차 완료**: `caseWorksheet` 응답 구조, 리뷰 화면 편집 UI, `app.case_worksheet` 저장/재조회 경로, 외부 루브릭 schema/loader/validation scaffold, 교수자 수동 워크시트 검수 상태 저장/표시. 워크시트 템플릿 key source는 `CASE_WORKSHEET_SECTION_SPECS`/`case_worksheet_template_item_keys()`로 명시했다. 후속은 임상팀 확정 루브릭 콘텐츠, AI 채점 보정, 승인 후 잠금·재제출 정책.
|
||||||
- **C2 1차 완료**: 위기 신호는 엔진 전 중단, 109 안내, `safety_events` 적재, 교수자 알림 큐, `ideation_observed` 전달까지 배선했다. 후속은 임상 스크립트·서약 문안·감점 루브릭.
|
- **C2 1차 완료**: 위기 신호는 엔진 전 중단, 109 안내, `safety_events` 적재, 교수자 알림 큐, `ideation_observed` 전달까지 배선했다. 후속은 임상 스크립트·서약 문안·감점 루브릭.
|
||||||
- **C3 2차 완료**: `theory_mode`가 세션·평가·생성 프롬프트까지 흐르고, 학습자는 세션 시작 전 기존 3개 모드 중 하나를 명시 선택해 `POST /sessions`로 보낸다. 후속은 임상팀 CBT 체인·이론부합 루브릭.
|
- **C3 2차 완료**: `theory_mode`가 세션·평가·생성 프롬프트까지 흐르고, 학습자는 세션 시작 전 기존 3개 모드 중 하나를 명시 선택해 `POST /sessions`로 보낸다. 후속은 임상팀 CBT 체인·이론부합 루브릭.
|
||||||
- **H2 1차+라이브 코칭+리뷰 2열 UI 완료**: `make_eval_hook`과 stream 평가가 턴 파이프라인에 붙고 정규화 테이블로 적재·복원된다. 대안발화도 `app.alternative_utterance`로 정규화한다. 라이브 코칭은 0615 워크북·DSM·공식 지침 요약/RAG 근거로 코칭 아바타 말풍선·근거 모달·발화별 이력 오버레이까지 연결했다. `POST /kb/live-coach/source-packs/sync`는 공용 source pack sync service를 통해 evaluator 전용 RAG에 증분 색인하고, hash 변경 시 document version을 최신+1로 올린다. 학습자 `SessionReview` 데스크톱은 좌측 축어록 타임라인과 우측 작업열의 2열 구조로 재배치했고, 교수자/모바일 레이아웃은 기존 규칙을 유지한다. 후속은 골든셋·임상팀 루브릭·source pack 임상 검수 상태 운영.
|
- **H2 1차+라이브 코칭+리뷰 2열 UI 완료**: `make_eval_hook`과 stream 평가가 턴 파이프라인에 붙고 정규화 테이블로 적재·복원된다. 대안발화도 `app.alternative_utterance`로 정규화한다. 라이브 코칭은 0615 워크북·DSM·공식 지침 요약/RAG 근거로 코칭 아바타 말풍선·근거 모달·발화별 이력 오버레이까지 연결했다. `POST /kb/live-coach/source-packs/sync`는 공용 source pack sync service를 통해 evaluator 전용 RAG에 증분 색인하고, hash 변경 시 document version을 최신+1로 올린다. 관리자 sync/CLI는 source pack manifest 생성 전 process-local cache를 refresh해 현재 repo JSON 기준으로 비교한다. 학습자 `SessionReview` 데스크톱은 좌측 축어록 타임라인과 우측 작업열의 2열 구조로 재배치했고, 교수자/모바일 레이아웃은 기존 규칙을 유지한다. 후속은 골든셋·임상팀 루브릭·source pack 임상 검수 상태 운영.
|
||||||
- **H3 10차 완료**: 페르소나 저작 CRUD(draft→review), P4~P7 저장소 JSON 로드, RAG source 기반 draft generation, prompt bundle id/version/hash provenance와 seed/version materializer runner가 붙었다. seed runner는 dry-run/JSON manifest를 제공하고, `--apply`에서만 DB pool을 초기화해 기존 materializer를 호출한다. repo-managed source pack runner는 DB-backed dry-run/apply를 제공하며 active `content_hash`가 바뀐 문서만 최신 version+1로 sync한다. `kb.raw_source_artifact` hash-only 레코드와 `rag.index_document()` raw/sensitivity=3 fail-closed guard를 추가해 raw 원문이 `kb.chunk`/embedding/FTS로 들어가지 않게 했고, `app/persona_read_model.py`로 persona DTO/mapper 경계를 분리했다. PersonaStudio의 자동사고·회기 시나리오·말투 목록·역린/금기 목록은 행 추가/삭제 UI로 바꾸고 기존 배열 payload 계약을 유지한다. 이번 패스에서 프롬프트 미리보기 raw JSON textarea를 라벨형 검토 섹션으로 교체했다. 후속은 루브릭·이론 콘텐츠 외부화, 임상팀 최종 검수 evidence, 암호화 blob/vault 기반 원문 실저장.
|
- **H3 10차 완료**: 페르소나 저작 CRUD(draft→review), P4~P7 저장소 JSON 로드, RAG source 기반 draft generation, prompt bundle id/version/hash provenance와 seed/version materializer runner가 붙었다. seed runner는 dry-run/JSON manifest를 제공하고, `--apply`에서만 DB pool을 초기화해 기존 materializer를 호출한다. repo-managed source pack runner는 DB-backed dry-run/apply를 제공하며 active `content_hash`가 바뀐 문서만 최신 version+1로 sync한다. `kb.raw_source_artifact` hash-only 레코드와 `rag.index_document()` raw/sensitivity=3 fail-closed guard를 추가해 raw 원문이 `kb.chunk`/embedding/FTS로 들어가지 않게 했고, `app/persona_read_model.py`로 persona DTO/mapper 경계를 분리했다. PersonaStudio의 자동사고·회기 시나리오·말투 목록·역린/금기 목록은 행 추가/삭제 UI로 바꾸고 기존 배열 payload 계약을 유지한다. 이번 패스에서 프롬프트 미리보기 raw JSON textarea를 라벨형 검토 섹션으로 교체했다. 후속은 루브릭·이론 콘텐츠 외부화, 임상팀 최종 검수 evidence, 암호화 blob/vault 기반 원문 실저장.
|
||||||
- **H4(부분)·X1·X2**: 한국어 날짜/금액/주소 및 이름/기관 로컬 휴리스틱 마스킹, 15-case 합성 fixture 평가 harness와 input/report schema(summary-only `technical_dry_run` report), 자연 발화형 이름 라벨·자기소개 마스킹 보강, 외부 LLM 호출 metadata-only `audit.llm_call_log` 적재 경로, 학습자 동의 수락/철회/회기 시작 하드게이트 골격은 완료했다. 모델 기반 정밀 ko NER, 실제 운영 말뭉치 기반 평가, guardian/legal 서명 evidence는 후속이다. dry-run JSONL export·PII scan·IAA 계산 1차도 완료했고 `ds.*` write는 `--write-dataset` 명시 시에만 수행한다. X2 비용 관측·예산 경고, evaluator fast/deep 모델 override, evaluator semantic cache, 운영 hit-rate 관측, 일별 비용 추이, 모델별 비용 검증 리포트는 완료했고 자동 차단·한도 enforcement 정책은 후속. M1은 provider_events 보존 슬롯, 내부 taxonomy, 인증 리뷰용 제한 파생 칩까지 완료했고, M2는 보수적 identity/agreement pinned_fact 자동 실적재, append-only history, 명시적 상담 약속 철회 contradiction, episodic embedding writer까지 완료했다. L1은 Node.js conformance runner와 session/persona read-model 분리까지 완료했다. 실제 provider 기반 한숨·울음 감지는 후속.
|
- **H4(부분)·X1·X2**: 한국어 날짜/금액/주소 및 이름/기관 로컬 휴리스틱 마스킹, 15-case 합성 fixture 평가 harness와 input/report schema(summary-only `technical_dry_run` report), 자연 발화형 이름 라벨·자기소개 마스킹 보강, optional ko recognizer adapter 인터페이스와 fake/failure 회귀, 외부 LLM 호출 metadata-only `audit.llm_call_log` 적재 경로, 학습자 동의 수락/철회/회기 시작 하드게이트 골격은 완료했다. 실제 ko recognizer 모델/provider 선정, 운영 말뭉치 기반 평가, guardian/legal 서명 evidence는 후속이다. dry-run JSONL export·PII scan·IAA 계산 1차도 완료했고 `ds.*` write는 `--write-dataset` 명시 시에만 수행한다. X2 비용 관측·예산 경고, evaluator fast/deep 모델 override, evaluator semantic cache, 운영 hit-rate 관측, 일별 비용 추이, 모델별 비용 검증 리포트는 완료했고 자동 차단·한도 enforcement 정책은 후속. M1은 provider_events 보존 슬롯, 내부 taxonomy, 인증 리뷰용 제한 파생 칩까지 완료했고, M2는 보수적 identity/agreement pinned_fact 자동 실적재, append-only history, 명시적 상담 약속 철회 contradiction, episodic embedding writer, 다음 턴 EngineMessage 주입 회귀까지 완료했다. L1은 Node.js conformance runner와 session/persona read-model 분리까지 완료했다. 실제 provider 기반 한숨·울음 감지는 후속.
|
||||||
|
|
||||||
### B. 소유자 결정 / 외부(임상팀·기관) 의존
|
### B. 소유자 결정 / 외부(임상팀·기관) 의존
|
||||||
|
|
||||||
|
|
@ -154,10 +154,12 @@ npm run e2e # Playwright — web+api+DB 스택 필요
|
||||||
- OCR 잔재·오탈자 존재(doc1·doc5). 의미는 시각 보정했으나 일부 표현 불확실.
|
- OCR 잔재·오탈자 존재(doc1·doc5). 의미는 시각 보정했으나 일부 표현 불확실.
|
||||||
- **doc4 κ/ICC·환각률은 신청서 본문 미명시** — 계약 확정 지표로 단정 금지.
|
- **doc4 κ/ICC·환각률은 신청서 본문 미명시** — 계약 확정 지표로 단정 금지.
|
||||||
- doc4/doc3 행정 불일치(참여교수 1명 vs 2명, 서식 연도 '2025' 오기, 연구책임자 표기 불일치, 트웬티온스 성명 공란).
|
- doc4/doc3 행정 불일치(참여교수 1명 vs 2명, 서식 연도 '2025' 오기, 연구책임자 표기 불일치, 트웬티온스 성명 공란).
|
||||||
- '현재상태'는 grep/코드 사실 기반 적대적 비평 인용에서 시작했으나, **C1·C2·C3·H1·H2·H3·M1 provider_events 보존/taxonomy/인증 리뷰 파생 칩·M2 case memory/pinned_fact/history/명시철회 contradiction/episodic embedding writer·M3, H4 로컬 마스킹/fixture 평가/감사/온보딩 경로, X1 dry-run export, X2 비용 관측·evaluator routing/cache/hit-rate 관측·모델별 비용 리포트, L1 engine gateway contract/golden/schema/Node conformance runner/session/persona read-model 분리는 구현 후 재검증 완료** 기준이다. 다만 H4의 모델 기반 정밀 NER, guardian/legal evidence, 공개 OAuth `/turn` proof, M1 실제 provider 기반 한숨·울음 감지는 여전히 게이트로 남아 있다. L1 외부 거버넌스는 FastAPI 유지 사유와 Node 전환 계획의 제출/등재 증거가 남아 있다.
|
- '현재상태'는 grep/코드 사실 기반 적대적 비평 인용에서 시작했으나, **C1·C2·C3·H1·H2·H3·M1 provider_events 보존/taxonomy/인증 리뷰 파생 칩·M2 case memory/pinned_fact/history/명시철회 contradiction/episodic embedding writer/다음턴 주입회귀/digest contract-only·M3, H4 로컬 마스킹/optional ko adapter/fixture 평가/감사/온보딩 경로, X1 dry-run export, X2 비용 관측·evaluator routing/cache/hit-rate 관측·모델별 비용 리포트, L1 engine gateway contract/golden/schema/Node conformance runner/session/persona read-model 분리는 구현 후 재검증 완료** 기준이다. M2는 fallback digest와 `digest_pending` 응답 계약까지이며, LLM digest worker 실행·품질평가·재압축은 완료로 보지 않는다. 다만 H4의 실제 ko recognizer 모델/provider 선정과 운영 corpus 평가, guardian/legal evidence, 공개 OAuth `/turn` proof, M1 실제 provider 기반 한숨·울음 감지는 여전히 게이트로 남아 있다. L1 외부 거버넌스는 FastAPI 유지 사유와 Node 전환 계획의 제출/등재 증거가 남아 있다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 변경 이력
|
### 변경 이력
|
||||||
|
|
||||||
|
- 2026-06-28: M2 8차 digest contract-only 경계와 focused 74 passed 검증 기준 반영.
|
||||||
|
- 2026-06-28: H4 optional ko recognizer adapter 경계와 focused 47 passed 검증 기준 반영.
|
||||||
- 2026-06-26: 초판. `docs/ops/source-docs-gap-analysis-2026-06-26.md`와 SSOT 대시보드 "원천문서 갭 분석" 섹션을 요약·인덱스화.
|
- 2026-06-26: 초판. `docs/ops/source-docs-gap-analysis-2026-06-26.md`와 SSOT 대시보드 "원천문서 갭 분석" 섹션을 요약·인덱스화.
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
분류: **B1 비차단 폴리시** · **B2 환경 제약(증거 생산 불가)** · **B3 소유자 결정** · **B4 외부 거버넌스**
|
분류: **B1 비차단 폴리시** · **B2 환경 제약(증거 생산 불가)** · **B3 소유자 결정** · **B4 외부 거버넌스**
|
||||||
|
|
||||||
> **B0. 원천문서 갭 분석 (2026-06-26 추가)** — 한신대 산학협력 원천문서 5종 정독으로 도출한 "부족한 부분"(critical 3 / high 4 / medium+ 6)은 **SSOT 대시보드** `docs/dev_dashboard.html` "원천문서 갭 분석" 섹션과 상세 `docs/ops/source-docs-gap-analysis-2026-06-26.md`에서 추적한다. C1 사례개념화 산출물은 저장형 워크시트와 외부 루브릭 scaffold까지 3차 구조를 만들었고, C2 위기개입 프로토콜은 1차 구조, C3 이론모드는 2차 명시 선택 UI까지 만들었다. 콘텐츠 정의는 임상팀(구훈정·어유경) 소유라 코드는 구조를 선제 구축하되 임상 문안과 평가기준은 외부 정의로 받는다.
|
> **B0. 원천문서 갭 분석 (2026-06-26 추가)** — 한신대 산학협력 원천문서 5종 정독으로 도출한 "부족한 부분"(critical 3 / high 4 / medium+ 6)은 **SSOT 대시보드** `docs/dev_dashboard.html` "원천문서 갭 분석" 섹션과 상세 `docs/ops/source-docs-gap-analysis-2026-06-26.md`에서 추적한다. C1 사례개념화 산출물은 저장형 워크시트, 외부 루브릭 scaffold, 교수자 수동 검수 상태 저장까지 4차 구조를 만들었고, C2 위기개입 프로토콜은 1차 구조, C3 이론모드는 2차 명시 선택 UI까지 만들었다. 콘텐츠 정의는 임상팀(구훈정·어유경) 소유라 코드는 구조를 선제 구축하되 임상 문안과 평가기준은 외부 정의로 받는다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
- [x] **회기 아카이브 저장/복원 API** — (2026-06-28 처리) `/learn/history`의 `보관됨`을 실제 학습자별 보기 상태로 연결했다. 종료 회기는 `POST /sessions/{id}/archive`/`restore`로 보관·복원하고, `app.session_archive_state`는 삭제가 아니라 `archived_at`/`updated_at`만 저장한다. `LearnerSessionSummary.archived`, `SessionArchiveResponse`, `LearnerDashboardOverview.archived_sessions`를 OpenAPI에 고정했고, 보관된 회기는 리뷰 대기 행동 큐에서 빠진다. 회기·턴·리뷰·공유 링크·연구/감사 증거는 삭제하지 않는다. **검증: `python -B -m pytest app/ -q` 178 passed, `python -B -m pytest engine_gateway/ -q` 11 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npx playwright test e2e/learner.spec.ts --project=chromium-desktop --workers=1` 6 passed.**
|
- [x] **회기 아카이브 저장/복원 API** — (2026-06-28 처리) `/learn/history`의 `보관됨`을 실제 학습자별 보기 상태로 연결했다. 종료 회기는 `POST /sessions/{id}/archive`/`restore`로 보관·복원하고, `app.session_archive_state`는 삭제가 아니라 `archived_at`/`updated_at`만 저장한다. `LearnerSessionSummary.archived`, `SessionArchiveResponse`, `LearnerDashboardOverview.archived_sessions`를 OpenAPI에 고정했고, 보관된 회기는 리뷰 대기 행동 큐에서 빠진다. 회기·턴·리뷰·공유 링크·연구/감사 증거는 삭제하지 않는다. **검증: `python -B -m pytest app/ -q` 178 passed, `python -B -m pytest engine_gateway/ -q` 11 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npx playwright test e2e/learner.spec.ts --project=chromium-desktop --workers=1` 6 passed.**
|
||||||
- [x] **OpenAI TTS voice preset DB map 연결** — (2026-06-28 처리) `app.persona_voice_map`의 OpenAI row를 `/voice/ws` TTS voice 선택에 연결했다. 명시 query preset은 DB map보다 우선하고, 세션 바인딩은 `app.sessions.persona_id/persona_version`으로 voice map을 찾으며, dev persona 생성은 catalog persona id/version map을 사용한다. seed materializer는 기본 OpenAI voice map을 `ON CONFLICT DO NOTHING`으로 생성하고, dev 런타임 스키마 보강은 기존 DB의 `persona_voice_map` 누락도 복구한다. OpenAI가 아닌 provider row는 기존 persona-code fallback으로 안전하게 흡수한다. **검증: `pytest app/test_runtime_policy.py app/test_persona_review.py app/test_voice_service.py app/test_voice_ws.py -q` 75 passed, `pytest app/ -q` 178 passed, `pytest engine_gateway/ -q` 11 passed.**
|
- [x] **OpenAI TTS voice preset DB map 연결** — (2026-06-28 처리) `app.persona_voice_map`의 OpenAI row를 `/voice/ws` TTS voice 선택에 연결했다. 명시 query preset은 DB map보다 우선하고, 세션 바인딩은 `app.sessions.persona_id/persona_version`으로 voice map을 찾으며, dev persona 생성은 catalog persona id/version map을 사용한다. seed materializer는 기본 OpenAI voice map을 `ON CONFLICT DO NOTHING`으로 생성하고, dev 런타임 스키마 보강은 기존 DB의 `persona_voice_map` 누락도 복구한다. OpenAI가 아닌 provider row는 기존 persona-code fallback으로 안전하게 흡수한다. **검증: `pytest app/test_runtime_policy.py app/test_persona_review.py app/test_voice_service.py app/test_voice_ws.py -q` 75 passed, `pytest app/ -q` 178 passed, `pytest engine_gateway/ -q` 11 passed.**
|
||||||
- [x] **빈상태 컬럼 높이 여백** — (2026-06-28 처리) session-review 0건 상태는 1280px 이상에서 3컬럼 masonry를 쓰지 않고 빈 리뷰 전용 2컬럼 순차 레이아웃으로 전환한다. 가짜 기록·장식 콘텐츠를 넣지 않고, `EMPTY_REVIEW_SESSION_ID` fixture와 `session-review-empty` visual gate로 390/720/861/900/1024/1280/1440 전 폭을 검증한다. session-prestart와 learner-home은 기존 gate 대상에 남겨 함께 무회귀 확인했다. **검증: `npm run typecheck`, `npm run build`, `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 e2e/session-review.spec.ts --project=chromium-desktop --workers=1` 6 passed.**
|
- [x] **빈상태 컬럼 높이 여백** — (2026-06-28 처리) session-review 0건 상태는 1280px 이상에서 3컬럼 masonry를 쓰지 않고 빈 리뷰 전용 2컬럼 순차 레이아웃으로 전환한다. 가짜 기록·장식 콘텐츠를 넣지 않고, `EMPTY_REVIEW_SESSION_ID` fixture와 `session-review-empty` visual gate로 390/720/861/900/1024/1280/1440 전 폭을 검증한다. session-prestart와 learner-home은 기존 gate 대상에 남겨 함께 무회귀 확인했다. **검증: `npm run typecheck`, `npm run build`, `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 e2e/session-review.spec.ts --project=chromium-desktop --workers=1` 6 passed.**
|
||||||
|
- [ ] **다크 UI v2 시안 실제 적용 라운드** — (2026-06-28 PLAN) `03-session-responsive-v2-dark-unified.png`에서 확인한 단일 dark surface 방향을 실제 코드 UI로 옮긴다. 우선순위는 P1 라이브 상담 세션(`Session.tsx`/`session.css`) → P2 회기 리뷰(`SessionReview.tsx`/`session-review.css`) → P3 학습자 홈(`LearnerHome.tsx`/`learner-home.css`) 순서다. 생성 PNG는 앱에 직접 넣지 않고 레이아웃·밀도·톤 기준으로만 사용한다. 실행 계획과 검증 게이트는 `docs/ops/dark-ui-refresh-implementation-plan-2026-06-28.md`에 고정했다. **완료 조건: 각 화면의 실제 Playwright 캡처가 v2 방향과 맞고, `typecheck`, `build`, 관련 focused E2E, `layout-visual-gate` 통과. DONE 표기는 새 캡처와 테스트 증거 이후에만 가능.**
|
||||||
- [x] **운영 티켓·헬스 샘플 이력 저장 모델 1차** — `/admin` 운영 홈과 `/admin/tickets`가 `app.admin_health_event`·`app.support_ticket` 실제 DB 모델에 연결됐다. `/users/support-tickets`는 인증 사용자의 문제·불만·장애 접수를 저장하고, `/admin/tickets`는 미해결 큐·우선순위·상태 변경을 처리한다. `/admin/uptime`은 상시 SLA가 아니라 관리자 콘솔이 관측한 헬스 샘플 정상률/중단 이력을 반환한다. 가짜 티켓 수·가짜 다운타임 숫자는 계속 금지. **검증: `python -m pytest app/test_admin_ops.py -q` 3 passed, `npm run generate:api-types`, `npm run typecheck`.**
|
- [x] **운영 티켓·헬스 샘플 이력 저장 모델 1차** — `/admin` 운영 홈과 `/admin/tickets`가 `app.admin_health_event`·`app.support_ticket` 실제 DB 모델에 연결됐다. `/users/support-tickets`는 인증 사용자의 문제·불만·장애 접수를 저장하고, `/admin/tickets`는 미해결 큐·우선순위·상태 변경을 처리한다. `/admin/uptime`은 상시 SLA가 아니라 관리자 콘솔이 관측한 헬스 샘플 정상률/중단 이력을 반환한다. 가짜 티켓 수·가짜 다운타임 숫자는 계속 금지. **검증: `python -m pytest app/test_admin_ops.py -q` 3 passed, `npm run generate:api-types`, `npm run typecheck`.**
|
||||||
- [x] **사용자별 운영 티켓 조회 UI** — (2026-06-28 처리) 학습자 Settings에 `지원 요청` 섹션을 추가해 본인이 접수한 티켓의 상태·카테고리·우선순위·담당 그룹·해결 메모를 읽기 전용으로 확인한다. `GET /users/support-tickets`는 `reporter_id = current user` 기준으로만 조회하고, 본문과 신고자 이메일/이름은 응답·감사 검증에서 제외한다. **검증: `python -B -m pytest app/test_user_support_tickets.py app/test_admin_ops.py app/test_runtime_policy.py -q` 29 passed, `npm run generate:api-types`, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npx playwright test e2e/settings.spec.ts --project=chromium-desktop --workers=1` 7 passed, `npx playwright test e2e/admin.spec.ts --project=chromium-desktop --workers=1 --grep "operation tickets"` 1 passed.**
|
- [x] **사용자별 운영 티켓 조회 UI** — (2026-06-28 처리) 학습자 Settings에 `지원 요청` 섹션을 추가해 본인이 접수한 티켓의 상태·카테고리·우선순위·담당 그룹·해결 메모를 읽기 전용으로 확인한다. `GET /users/support-tickets`는 `reporter_id = current user` 기준으로만 조회하고, 본문과 신고자 이메일/이름은 응답·감사 검증에서 제외한다. **검증: `python -B -m pytest app/test_user_support_tickets.py app/test_admin_ops.py app/test_runtime_policy.py -q` 29 passed, `npm run generate:api-types`, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npx playwright test e2e/settings.spec.ts --project=chromium-desktop --workers=1` 7 passed, `npx playwright test e2e/admin.spec.ts --project=chromium-desktop --workers=1 --grep "operation tickets"` 1 passed.**
|
||||||
- [x] **상시 synthetic monitor/cron 샘플러 1차** — (2026-06-28 처리) `/admin/health`와 같은 헬스 계산을 `record_admin_health_sample()`로 공통화하고, 브라우저 세션 없이 `scripts/record-admin-health-sample.py`가 `app.admin_health_event`에 서비스별 샘플을 append한다. Windows 예약 실행은 `scripts/install-health-sampler-task.ps1`가 `VignetteAdminHealthSampler` Scheduled Task로 설치한다. 이 값은 SLA가 아니라 synthetic sampler가 관측한 최근 샘플 이력이다. **검증: `python -B -m py_compile apps/api/app/routes/admin.py apps/api/app/test_admin_ops.py scripts/record-admin-health-sample.py`, `python scripts/record-admin-health-sample.py --help`, `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-health-sampler-task.ps1 -PrintOnly -IntervalMinutes 7`, `python -B -m pytest app/test_admin_ops.py app/test_runtime_policy.py app/test_user_support_tickets.py -q` 31 passed, 로컬 one-shot `recorded_count=5`, `status=ok`, `engine_mode=claude_cli`.**
|
- [x] **상시 synthetic monitor/cron 샘플러 1차** — (2026-06-28 처리) `/admin/health`와 같은 헬스 계산을 `record_admin_health_sample()`로 공통화하고, 브라우저 세션 없이 `scripts/record-admin-health-sample.py`가 `app.admin_health_event`에 서비스별 샘플을 append한다. Windows 예약 실행은 `scripts/install-health-sampler-task.ps1`가 `VignetteAdminHealthSampler` Scheduled Task로 설치한다. 이 값은 SLA가 아니라 synthetic sampler가 관측한 최근 샘플 이력이다. **검증: `python -B -m py_compile apps/api/app/routes/admin.py apps/api/app/test_admin_ops.py scripts/record-admin-health-sample.py`, `python scripts/record-admin-health-sample.py --help`, `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-health-sampler-task.ps1 -PrintOnly -IntervalMinutes 7`, `python -B -m pytest app/test_admin_ops.py app/test_runtime_policy.py app/test_user_support_tickets.py -q` 31 passed, 로컬 one-shot `recorded_count=5`, `status=ok`, `engine_mode=claude_cli`.**
|
||||||
|
|
@ -31,7 +32,7 @@
|
||||||
- [ ] **운영 티켓 자동 분류·처리 후속** — Claude Recipe headless 자동 수정 후보, 관리자 승인 후 이슈 등록·PR/작업 스레드 생성, 자동 담당 그룹 배정, 우선순위 escalation, 처리 결과 audit trail 확장은 아직 설계/승인 필요. 자동 수정은 운영자 승인 전까지 실행하지 않는다.
|
- [ ] **운영 티켓 자동 분류·처리 후속** — Claude Recipe headless 자동 수정 후보, 관리자 승인 후 이슈 등록·PR/작업 스레드 생성, 자동 담당 그룹 배정, 우선순위 escalation, 처리 결과 audit trail 확장은 아직 설계/승인 필요. 자동 수정은 운영자 승인 전까지 실행하지 않는다.
|
||||||
- [x] **learner-home 로딩 스켈레톤 밀도(390)** — (2026-06-26 처리) '연습 대상' 로딩 스켈레톤을 빈 div 단일 셰이머에서 실제 카드 구조(마크 박스 + 이름/메타/요약 2줄 플레이스홀더)를 모사하도록 밀도 보강. `LearnerHome.tsx` (`.lh-skel__box`/`.lh-skel__line*`), reduced-motion 분기 동반 갱신. **검증: `npm run typecheck` PASS + `vite build` PASS.**
|
- [x] **learner-home 로딩 스켈레톤 밀도(390)** — (2026-06-26 처리) '연습 대상' 로딩 스켈레톤을 빈 div 단일 셰이머에서 실제 카드 구조(마크 박스 + 이름/메타/요약 2줄 플레이스홀더)를 모사하도록 밀도 보강. `LearnerHome.tsx` (`.lh-skel__box`/`.lh-skel__line*`), reduced-motion 분기 동반 갱신. **검증: `npm run typecheck` PASS + `vite build` PASS.**
|
||||||
|
|
||||||
검증 기준: 변경 후 `npm run typecheck`, `e2e/layout-visual-gate.spec.ts`(현재 9/9), 레이아웃 포커스 E2E(54), `e2e/session-layout.spec.ts`(8/8) 무회귀. 이번 후속 라운드는 빈상태 레이아웃(`typecheck`, `build`, layout gate 9/9, session-layout+session-review desktop 6/6), 사용자별 티켓 조회 UI(backend 29, settings 7, admin ticket 1), synthetic health sampler(backend 31, one-shot recorded_count 5), health retention/rollup(backend 36, dry-run OK), 티켓 중복 저장·수동 연결(backend 38, API types/typecheck/build OK, admin tickets E2E 2)을 확인했다.
|
검증 기준: 변경 후 `npm run typecheck`, `e2e/layout-visual-gate.spec.ts`(현재 9/9), 레이아웃 포커스 E2E(54), `e2e/session-layout.spec.ts`(8/8) 무회귀. 이번 후속 라운드는 빈상태 레이아웃(`typecheck`, `build`, layout gate 9/9, session-layout+session-review desktop 6/6), 사용자별 티켓 조회 UI(backend 29, settings 7, admin ticket 1), synthetic health sampler(backend 31, one-shot recorded_count 5), health retention/rollup(backend 36, dry-run OK), 티켓 중복 저장·수동 연결(backend 38, API types/typecheck/build OK, admin tickets E2E 2), M2 route-level seed recall 주입(py_compile, M2 focused 68)을 확인했다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -81,21 +82,21 @@
|
||||||
- P1 서연 음성 아트 PoC: Higgs v3 무참조 synthetic seed + 5개 정서/속도 변주(mp3/wav) 생성. 실존 reference voice 미사용. `/voice/ws`에는 dev-only sample TTS provider로 연결했고 live text_turn smoke에서 `p1-sample-poc` binary 20 chunks/78,573 bytes 수신 확인. 이 provider는 TTS override만 담당한다(마이크/STT는 OpenAI 키 필요). 프로덕션 탑재는 라이선스/권리 결정 전 금지.
|
- P1 서연 음성 아트 PoC: Higgs v3 무참조 synthetic seed + 5개 정서/속도 변주(mp3/wav) 생성. 실존 reference voice 미사용. `/voice/ws`에는 dev-only sample TTS provider로 연결했고 live text_turn smoke에서 `p1-sample-poc` binary 20 chunks/78,573 bytes 수신 확인. 이 provider는 TTS override만 담당한다(마이크/STT는 OpenAI 키 필요). 프로덕션 탑재는 라이선스/권리 결정 전 금지.
|
||||||
- 부수: RLS smoke 스크립트 cleanup RLS 컨텍스트 버그 수정, `vignette_app`(NOBYPASSRLS) dev 역할 프로비저닝.
|
- 부수: RLS smoke 스크립트 cleanup RLS 컨텍스트 버그 수정, `vignette_app`(NOBYPASSRLS) dev 역할 프로비저닝.
|
||||||
- 평가 SSOP 2차: `TurnRecord.evaluation`을 `app.feedback_scores`/`turn_technique`/`turn_client_state`/`supervisor_comment`/`alternative_utterance`로 정규화 적재·복원하고, submit/stream/voice 저장 경로를 `app.turn_runtime`으로 공통화했다. 원시 평가 테이블은 evaluator/admin RLS로 제한하고 서버가 evaluator 컨텍스트로 적재한다. `app.turns` insert는 `RETURNING id` 확인으로 silent success 제거. **검증: `pytest app/test_evaluation_persistence.py -q` 6 passed, 관련 회귀 45 passed, `pytest app/` 119 passed, `pytest engine_gateway/` 7 passed.**
|
- 평가 SSOP 2차: `TurnRecord.evaluation`을 `app.feedback_scores`/`turn_technique`/`turn_client_state`/`supervisor_comment`/`alternative_utterance`로 정규화 적재·복원하고, submit/stream/voice 저장 경로를 `app.turn_runtime`으로 공통화했다. 원시 평가 테이블은 evaluator/admin RLS로 제한하고 서버가 evaluator 컨텍스트로 적재한다. `app.turns` insert는 `RETURNING id` 확인으로 silent success 제거. **검증: `pytest app/test_evaluation_persistence.py -q` 6 passed, 관련 회귀 45 passed, `pytest app/` 119 passed, `pytest engine_gateway/` 7 passed.**
|
||||||
- 라이브 코칭 1차 + 이력 UI/RAG sync + 학습자 리뷰 2열 UI: `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`를 추가해 0615 사례개념화 워크북·DSM·공식 지침 요약 기반 코칭을 구조화했다. 세션 UI는 코칭 모드에서 AI 코치 아바타 말풍선, 근거 모달, 학습자 발화 우측 `C` 마커, 채팅 위 스크롤 이력 오버레이를 제공한다. 저장 payload는 PII 마스킹 excerpt와 코칭 구조화 JSON으로 제한한다. 공식 자료는 허가된 source pack의 version/citation/summary로만 코칭에 넣고 장문 원문·공식 문항 재현은 하지 않는다. 같은 source pack은 `app.services.source_pack_sync`로 evaluator 전용 RAG에 증분 색인하며, active `content_hash`가 바뀌면 `kb.document.version`을 최신+1로 올려 UNIQUE 충돌을 피한다. 학습자 `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 `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: sync 4 sources/15 chunks embedded, 재호출 skipped_unchanged 4, `official_suicide_risk_guidelines` source_id 필터 검색 성공.**
|
- 라이브 코칭 1차 + 이력 UI/RAG sync + 학습자 리뷰 2열 UI: `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`를 추가해 0615 사례개념화 워크북·DSM·공식 지침 요약 기반 코칭을 구조화했다. 세션 UI는 코칭 모드에서 AI 코치 아바타 말풍선, 근거 모달, 학습자 발화 우측 `C` 마커, 채팅 위 스크롤 이력 오버레이를 제공한다. 저장 payload는 PII 마스킹 excerpt와 코칭 구조화 JSON으로 제한한다. 공식 자료는 허가된 source pack의 version/citation/summary로만 코칭에 넣고 장문 원문·공식 문항 재현은 하지 않는다. 같은 source pack은 `app.services.source_pack_sync`로 evaluator 전용 RAG에 증분 색인하며, active `content_hash`가 바뀌면 `kb.document.version`을 최신+1로 올려 UNIQUE 충돌을 피한다. 2026-06-28 refactor-governance P4에서는 live turn의 프로세스 로컬 source pack snapshot은 유지하되 관리자 sync/CLI가 `refresh=True`로 캐시를 비우고 repo 파일을 다시 읽게 해 stale `content_hash` 비교를 막았다. 학습자 `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 `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`, `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: sync 4 sources/15 chunks embedded, 재호출 skipped_unchanged 4, `official_suicide_risk_guidelines` source_id 필터 검색 성공.**
|
||||||
- 계약 SSOT 4차: FastAPI OpenAPI를 `apps/web/src/lib/api.gen.ts`로 생성하는 `npm run generate:api-types`와 stale 체크용 `npm run check:api-types`를 유지한다. `TurnResponse`/`SessionEndResponse`에 이어 `MeResponse`, `AuthConfigResponse`, `ConsentResponse`, `PersonaSummary`, `PersonaReviewSummary`, admin/user/engine DTO, review leaf DTO, `CrisisResourceResponse`, teacher safety/growth leaf DTO, `LearnerSessionsResponse`, `LearnerDashboardResponse`, `SessionReviewResponse`, worksheet aggregate, `TeacherDashboardResponse`, `TeacherSessionSummary`, `TeacherLearnerGrowth`, `SessionStartResponse`, `SessionDetailResponse`, `SessionDetailTurn`를 생성 타입 alias로 전환했다. 세션 응답 stage는 `StageLabel` enum으로 OpenAPI에 고정했고, generated optional/default 차이는 UI form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. `.github/workflows/api-contract.yml`은 PR/master push에서 `npm run check:api-types` drift를 막는다. **검증: `npm run check:api-types`, `npm run typecheck`, `npm run build`, `python -m pytest app/test_session_turn_persistence.py -q` 16 passed, `learner.spec.ts + session-review.spec.ts + teacher.spec.ts + layout-visual-gate.spec.ts` 32 passed.**
|
- 계약 SSOT 7차: FastAPI OpenAPI를 `apps/web/src/lib/api.gen.ts`로 생성하는 `npm run generate:api-types`와 stale 체크용 `npm run check:api-types`를 유지한다. `TurnResponse`/`SessionEndResponse`에 이어 `MeResponse`, `AuthConfigResponse`, `ConsentResponse`, `PersonaSummary`, `PersonaReviewSummary`, admin/user/engine DTO, review leaf DTO, `CrisisResourceResponse`, teacher safety/growth leaf DTO, `LearnerSessionsResponse`, `LearnerDashboardResponse`, `SessionReviewResponse`, worksheet aggregate, `TeacherDashboardResponse`, `TeacherSessionSummary`, `TeacherLearnerGrowth`, `SessionStartResponse`, `SessionDetailResponse`, `SessionDetailTurn`를 생성 타입 alias로 전환했다. 세션 응답 stage는 `StageLabel` enum으로 OpenAPI에 고정했고, 69차에서는 `ReviewPhaseSegment.key/label`, `SessionReviewResponse.reachedPhase`, `EvaluationSummary.stage`, 평가 트리거 응답 stage, `TeacherSessionSummary.stage`, `TeacherGrowthPoint.stage`도 union/null 계약으로 축소했다. 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`의 중복 stage 규칙을 제거했다. generated optional/default 차이는 UI form state, notification default helper, 배열 렌더링 fallback, live-coach `단계 미상` 표시로 흡수한다. `.github/workflows/api-contract.yml`은 PR/master push에서 `npm run check:api-types` drift를 막는다. **검증: `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, `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, `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`, 과거 `learner.spec.ts + session-review.spec.ts + teacher.spec.ts + layout-visual-gate.spec.ts` 32 passed.**
|
||||||
- 위기게이트 109 1차: 실제 자해·자살 신호는 LLM/엔진 호출 전 중단하고 `crisis_resource(109)`와 `conversation_stopped`를 REST/SSE/voice 응답에 싣는다. `app.safety_events` detail 적재와 교수자 대시보드 안전 알림 큐까지 연결했다. 안전 이벤트 DB insert payload도 회귀화했다. 임상 문안, 실시간 push/메일, 지역 자원 확장은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 20 passed, `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed.**
|
- 위기게이트 109 1차: 실제 자해·자살 신호는 LLM/엔진 호출 전 중단하고 `crisis_resource(109)`와 `conversation_stopped`를 REST/SSE/voice 응답에 싣는다. `app.safety_events` detail 적재와 교수자 대시보드 안전 알림 큐까지 연결했다. 안전 이벤트 DB insert payload도 회귀화했다. 임상 문안, 실시간 push/메일, 지역 자원 확장은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 20 passed, `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed.**
|
||||||
- 교수자 성장 추적/리뷰 상세 진입 1차: `teacher_dashboard`가 정규화된 턴 평가를 기반으로 학습자별 적절성·라포·기술 사용 추이를 집계하고, 교수 콘솔에 최근 회기/항목별 성장 카드와 안전 알림 큐를 함께 표시한다. 종료 회기 리뷰 대기 행과 최근 회기 행은 `/teach/session/:sessionId/review`로 이어지고, 교수자는 회기 리뷰와 사례개념화 워크시트를 읽기 전용으로 검토한다. 학습자 워크시트 저장은 계속 learner 전용이다. **검증: `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`, `npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1` 4 passed.**
|
- 교수자 성장 추적/리뷰 상세 진입 1차: `teacher_dashboard`가 정규화된 턴 평가를 기반으로 학습자별 적절성·라포·기술 사용 추이를 집계하고, 교수 콘솔에 최근 회기/항목별 성장 카드와 안전 알림 큐를 함께 표시한다. 종료 회기 리뷰 대기 행과 최근 회기 행은 `/teach/session/:sessionId/review`로 이어지고, 교수자는 회기 리뷰와 사례개념화 워크시트를 읽기 전용으로 검토한다. 학습자 워크시트 저장은 계속 learner 전용이다. **검증: `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`, `npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1` 4 passed.**
|
||||||
- 교수자 검토 상태 1차: `app.session_review_status`에 교수자 회기 검토 상태를 저장하고, teacher dashboard 응답에 `review_status/review_note/reviewed_at`를 포함한다. `PUT /teacher/sessions/{session_id}/review-status`로 교수자 메모 저장과 검토 완료 처리를 수행하며, 완료된 회기는 pending queue에서 제외한다. 교수자/관리자의 `/sessions/{id}/review` 읽기 허용은 유지하고, 학습자 워크시트 저장은 learner 전용으로 계속 제한한다. 교수 콘솔 검토 큐는 숨김 처리하지 않고 상단 triage로 노출하며, 학습자 리뷰 화면에는 교수자 전용 grid 빈칸을 예약하지 않는다. **검증: `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, `session-review.spec.ts` 2 passed, `layout-visual-gate.spec.ts` 7 passed.**
|
- 교수자 검토 상태 1차: `app.session_review_status`에 교수자 회기 검토 상태를 저장하고, teacher dashboard 응답에 `review_status/review_note/reviewed_at`를 포함한다. `PUT /teacher/sessions/{session_id}/review-status`로 교수자 메모 저장과 검토 완료 처리를 수행하며, 완료된 회기는 pending queue에서 제외한다. 교수자/관리자의 `/sessions/{id}/review` 읽기 허용은 유지하고, 학습자 워크시트 저장은 learner 전용으로 계속 제한한다. 교수 콘솔 검토 큐는 숨김 처리하지 않고 상단 triage로 노출하며, 학습자 리뷰 화면에는 교수자 전용 grid 빈칸을 예약하지 않는다. **검증: `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, `session-review.spec.ts` 2 passed, `layout-visual-gate.spec.ts` 7 passed.**
|
||||||
- 학습자 개인화 대시보드 1차: 교수자 성장 집계와 중복되던 점수/라포/기법 계산을 `app.services.session_metrics`로 공용화하고, `GET /sessions/dashboard`가 본인 세션 기반 `overview/growth/persona_progress/achievements/recent_feedback`를 반환한다. 학습자 홈은 누적 회기, 리뷰 대기, 최근 평가, 라포 흐름, 페르소나별 진행, 최근 피드백, 마지막 세션 리캡을 실제 서버 데이터로 표시한다. 성취는 공식 등급/수료가 아니라 실제 연습 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, `teacher.spec.ts` 4 passed, `layout-visual-gate.spec.ts` 7 passed, `session-layout.spec.ts` 4 passed.**
|
- 학습자 개인화 대시보드 1차: 교수자 성장 집계와 중복되던 점수/라포/기법 계산을 `app.services.session_metrics`로 공용화하고, `GET /sessions/dashboard`가 본인 세션 기반 `overview/growth/persona_progress/achievements/recent_feedback`를 반환한다. 학습자 홈은 누적 회기, 리뷰 대기, 최근 평가, 라포 흐름, 페르소나별 진행, 최근 피드백, 마지막 세션 리캡을 실제 서버 데이터로 표시한다. 성취는 공식 등급/수료가 아니라 실제 연습 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, `teacher.spec.ts` 4 passed, `layout-visual-gate.spec.ts` 7 passed, `session-layout.spec.ts` 4 passed.**
|
||||||
- 페르소나 스튜디오/RAG 저작 10차: 교수 콘솔의 JSON 패널을 분리해 teacher/admin 전용 `/teach/personas` 3열 스튜디오(좌: 저작 흐름·카탈로그·검수 큐, 중앙: RAG 자료 등록·항목형 편집 탭, 우: 검증·RAG 근거·검수 결정)로 옮겼다. 2026-06-27 UI 정리에서 중복 네이티브 파일 입력을 커스텀 SSOT 첨부 드롭존으로 교체하고, 저작 스테퍼·탭별 작성 가이드·검증 dot 정렬을 보강했다. 2026-06-28 항목형 목록 UI 정리에서 자동사고, 회기 시나리오, 말투 filler/verbal tic/nonverbal cue, 역린·금기 응답·금기어를 행 추가/삭제 UI로 전환했고, 저장 payload는 기존 배열 schema를 유지하며 빈 항목만 저장 직전에 제거한다. 프롬프트 탭은 raw JSON textarea 대신 L1 카드·인적 범주·임상 배경·말투·수치 파라미터·역린·회기 시나리오·추가 계약 섹션으로 같은 draft 데이터를 라벨형 검토 UI에 표시한다. 레이아웃 근거와 생성 시안은 `docs/ops/layout-research-2026-06-27/persona-dashboard-layout-guideline.md`에 묶었다. `POST /personas/sources`는 첨부/붙여넣기 자료를 PII 마스킹 후 raw 원문 hash-only 증거를 `kb.raw_source_artifact`에 따로 기록하고, sanitized 파생본만 `kb.source/document/chunk`에 evaluator 전용(`visible_to=['evaluator']`, `sensitivity=2`) 근거 문서로 등록한다. `rag.index_document()`는 `sensitivity=3` 또는 raw marker chunk를 DB 접근 전에 차단해 raw 원문이 `kb.chunk`/embedding/FTS에 들어가지 않게 한다. `POST /personas/drafts/generate`는 `source_id` 기반 RAG 검색 결과만 생성 프롬프트에 넘긴다. 생성 응답과 draft `source_provenance`에는 source id, doc/content hash, chunk id, prompt bundle id/version/hash(`persona-draft-rag@2026-06-28.1`)를 남긴다. P1~P7 시스템 페르소나는 DB 저작 카탈로그의 초기 부트스트랩으로 승격했고, `materialize_seed_personas()`는 누락분만 insert해서 교수 편집본이나 `archived` 보관본을 덮어쓰지 않는다. `scripts/materialize-persona-seeds.py`는 같은 seed/version manifest를 dry-run 기본으로 보고하며, `--apply`일 때만 DB pool을 초기화한 뒤 기존 idempotent materializer를 호출한다. `scripts/sync-persona-sources.py`와 `app.services.source_pack_sync`는 repo-managed source pack의 active `content_hash`를 DB에서 비교하고, 변경 시 `kb.document.version`을 최신+1로 색인한다. 교수자는 공개 목록에서 승인본을 다음 버전 draft로 복제해 수정하고, 불필요한 페르소나는 기존 회기 FK 보존을 위해 같은 code family 전체를 `archived`로 보관 처리한다. `app/persona_read_model.py`는 catalog/review/draft/source/evidence DTO와 mapper를 route에서 분리해 schema 이름과 `source/degraded` fail-closed 경계를 유지한다. 암호화 blob/vault 기반 원문 실저장, 임상팀 최종 검수/서면 evidence, 루브릭·이론 콘텐츠 외부화는 후속. **검증: `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, `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` 33 passed, `python -B -m pytest -p no:cacheprovider app/test_persona_review.py app/test_session_turn_persistence.py -q` 55 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `py -3.11 scripts\materialize-persona-seeds.py --json` dry-run `manifest_count=7`, `py -3.11 scripts\sync-persona-sources.py --help`, `npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1` 8 passed, 프롬프트 탭 포함 `layout-visual-gate.spec.ts` 9 passed, `session-layout.spec.ts` desktop/mobile 8 passed.**
|
- 페르소나 스튜디오/RAG 저작 10차: 교수 콘솔의 JSON 패널을 분리해 teacher/admin 전용 `/teach/personas` 3열 스튜디오(좌: 저작 흐름·카탈로그·검수 큐, 중앙: RAG 자료 등록·항목형 편집 탭, 우: 검증·RAG 근거·검수 결정)로 옮겼다. 2026-06-27 UI 정리에서 중복 네이티브 파일 입력을 커스텀 SSOT 첨부 드롭존으로 교체하고, 저작 스테퍼·탭별 작성 가이드·검증 dot 정렬을 보강했다. 2026-06-28 항목형 목록 UI 정리에서 자동사고, 회기 시나리오, 말투 filler/verbal tic/nonverbal cue, 역린·금기 응답·금기어를 행 추가/삭제 UI로 전환했고, 저장 payload는 기존 배열 schema를 유지하며 빈 항목만 저장 직전에 제거한다. 프롬프트 탭은 raw JSON textarea 대신 L1 카드·인적 범주·임상 배경·말투·수치 파라미터·역린·회기 시나리오·추가 계약 섹션으로 같은 draft 데이터를 라벨형 검토 UI에 표시한다. 레이아웃 근거와 생성 시안은 `docs/ops/layout-research-2026-06-27/persona-dashboard-layout-guideline.md`에 묶었다. `POST /personas/sources`는 첨부/붙여넣기 자료를 PII 마스킹 후 raw 원문 hash-only 증거를 `kb.raw_source_artifact`에 따로 기록하고, sanitized 파생본만 `kb.source/document/chunk`에 evaluator 전용(`visible_to=['evaluator']`, `sensitivity=2`) 근거 문서로 등록한다. `rag.index_document()`는 `sensitivity=3` 또는 raw marker chunk를 DB 접근 전에 차단해 raw 원문이 `kb.chunk`/embedding/FTS에 들어가지 않게 한다. `POST /personas/drafts/generate`는 `source_id` 기반 RAG 검색 결과만 생성 프롬프트에 넘긴다. 생성 응답과 draft `source_provenance`에는 source id, doc/content hash, chunk id, prompt bundle id/version/hash(`persona-draft-rag@2026-06-28.1`)를 남긴다. P1~P7 시스템 페르소나는 DB 저작 카탈로그의 초기 부트스트랩으로 승격했고, `materialize_seed_personas()`는 누락분만 insert해서 교수 편집본이나 `archived` 보관본을 덮어쓰지 않는다. `scripts/materialize-persona-seeds.py`는 같은 seed/version manifest를 dry-run 기본으로 보고하며, `--apply`일 때만 DB pool을 초기화한 뒤 기존 idempotent materializer를 호출한다. `scripts/sync-persona-sources.py`와 `app.services.source_pack_sync`는 repo-managed source pack의 active `content_hash`를 DB에서 비교하고, 변경 시 `kb.document.version`을 최신+1로 색인한다. 교수자는 공개 목록에서 승인본을 다음 버전 draft로 복제해 수정하고, 불필요한 페르소나는 기존 회기 FK 보존을 위해 같은 code family 전체를 `archived`로 보관 처리한다. `app/persona_read_model.py`는 catalog/review/draft/source/evidence DTO와 mapper를 route에서 분리해 schema 이름과 `source/degraded` fail-closed 경계를 유지한다. 암호화 blob/vault 기반 원문 실저장, 임상팀 최종 검수/서면 evidence, 루브릭·이론 콘텐츠 외부화는 후속. **검증: `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, `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` 33 passed, `python -B -m pytest -p no:cacheprovider app/test_persona_review.py app/test_session_turn_persistence.py -q` 55 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `py -3.11 scripts\materialize-persona-seeds.py --json` dry-run `manifest_count=7`, `py -3.11 scripts\sync-persona-sources.py --help`, `npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1` 8 passed, 프롬프트 탭 포함 `layout-visual-gate.spec.ts` 9 passed, `session-layout.spec.ts` desktop/mobile 8 passed.**
|
||||||
- M1 비언어 이벤트 4차: voice learner turn에 이미 저장하던 `audio_ref`/`silence_ms`/`speech_rate`/`barge_in`을 리뷰 API `nonverbal` 이벤트로 노출하고, 회기 리뷰 축어록에 침묵·발화 속도·끼어듦·음성 입력 칩을 표시했다. 2차에서는 `app.turns.provider_events JSONB`와 `TurnRecord.provider_events`를 추가해 WebSocket control/STT provider 이벤트를 allowlist·size limit 후 보존했고, 3차에서는 저장 전 sanitizer에서 내부 taxonomy `event_type`/`category`를 붙인다. 4차에서는 인증된 회기 리뷰에 한숨·울음·웃음·호흡·운율·배경소음 계열만 한글 label/detail 칩으로 파생 노출한다. raw transcript/text/provider/source/raw type은 응답에서 제외하고, 공개 공유 카드에는 축어록과 provider raw를 싣지 않는다. 실제 provider 기반 한숨·울음 감지, 역량 지표화 정책, live 마이크/WSS 장시간 실측은 후속. **검증: `python -B -m py_compile app/routes/sessions.py app/test_session_turn_persistence.py`, `python -B -m pytest -p no:cacheprovider app/test_voice_ws.py app/test_session_turn_persistence.py app/test_voice_service.py -q` 45 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1` 3 passed.**
|
- M1 비언어 이벤트 4차: voice learner turn에 이미 저장하던 `audio_ref`/`silence_ms`/`speech_rate`/`barge_in`을 리뷰 API `nonverbal` 이벤트로 노출하고, 회기 리뷰 축어록에 침묵·발화 속도·끼어듦·음성 입력 칩을 표시했다. 2차에서는 `app.turns.provider_events JSONB`와 `TurnRecord.provider_events`를 추가해 WebSocket control/STT provider 이벤트를 allowlist·size limit 후 보존했고, 3차에서는 저장 전 sanitizer에서 내부 taxonomy `event_type`/`category`를 붙인다. 4차에서는 인증된 회기 리뷰에 한숨·울음·웃음·호흡·운율·배경소음 계열만 한글 label/detail 칩으로 파생 노출한다. raw transcript/text/provider/source/raw type은 응답에서 제외하고, 공개 공유 카드에는 축어록과 provider raw를 싣지 않는다. 실제 provider 기반 한숨·울음 감지, 역량 지표화 정책, live 마이크/WSS 장시간 실측은 후속. **검증: `python -B -m py_compile app/routes/sessions.py app/test_session_turn_persistence.py`, `python -B -m pytest -p no:cacheprovider app/test_voice_ws.py app/test_session_turn_persistence.py app/test_voice_service.py -q` 45 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1` 3 passed.**
|
||||||
- M2 다회기 케이스 아크 6차: DB 세션 생성 시 `(persona_id, learner_id)` 기준 `case_profile`을 upsert하고, `session_no`를 트랜잭션 안에서 원자 증가시키며, `InProcSession.case_id`가 매회 새 `runtime_case_id`가 아니라 안정 `case_id`를 가리키게 수정했다. 시작/submit/stream/voice 경로는 case recall cache를 사용한다. 세션 종료 시 마스킹 축어록 기반 fallback `session_summary.digest`와 `case_profile.case_digest`, `rapport_trajectory`, `alliance_level`을 갱신하고, 다음 회기 seed recall에서 `case_digest`·직전 `session_summary`·client-visible non-contradicted `pinned_fact`를 함께 조립한다. 3차에서는 마스킹된 client-visible 발화에서 `[NAME]`/`[ORG]` identity와 명시적 상담 약속만 보수적으로 `pinned_fact`에 upsert했고, 4차에서는 삽입 또는 값 변경 시 `pinned_fact_history`에 append-only 이력을 남긴다. 5차에서는 명시적 상담 약속 철회/부정만 기존 non-locked `agreement:counseling` fact를 `contradicted`로 격리하고 history reason `contradiction`을 남긴다. 6차에서는 세션 종료 저장 성공 뒤 마스킹된 client-visible 내담자 발화만 `app.turn_embedding`에 BGE-M3 dense/sparse로 `ON CONFLICT (turn_id) DO NOTHING` 색인한다. 새 contradicted fact는 임의 생성하지 않고, 같은 값 재확인은 history를 늘리지 않으며, `locked` fact는 건드리지 않는다. learner-owned `case_profile` 기준 RLS insert/update를 추가했고, 관계갈등·위기·임상 추론은 자동 pinning/모순 처리에서 제외했다. 관계·임상 fact 승격 기준과 LLM digest 압축은 후속. **검증: `python -B -m py_compile app/services/rag.py app/routes/sessions.py app/test_session_memory.py`, `python -B -m pytest -p no:cacheprovider app/test_session_memory.py -q` 14 passed, `python -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 -q` 67 passed.**
|
- M2 다회기 케이스 아크 8차: DB 세션 생성 시 `(persona_id, learner_id)` 기준 `case_profile`을 upsert하고, `session_no`를 트랜잭션 안에서 원자 증가시키며, `InProcSession.case_id`가 매회 새 `runtime_case_id`가 아니라 안정 `case_id`를 가리키게 수정했다. 시작/submit/stream/voice 경로는 case recall cache를 사용한다. 세션 종료 시 마스킹 축어록 기반 fallback `session_summary.digest`와 `case_profile.case_digest`, `rapport_trajectory`, `alliance_level`을 갱신하고, 다음 회기 seed recall에서 `case_digest`·직전 `session_summary`·client-visible non-contradicted `pinned_fact`를 함께 조립한다. 3차에서는 마스킹된 client-visible 발화에서 `[NAME]`/`[ORG]` identity와 명시적 상담 약속만 보수적으로 `pinned_fact`에 upsert했고, 4차에서는 삽입 또는 값 변경 시 `pinned_fact_history`에 append-only 이력을 남긴다. 5차에서는 명시적 상담 약속 철회/부정만 기존 non-locked `agreement:counseling` fact를 `contradicted`로 격리하고 history reason `contradiction`을 남긴다. 6차에서는 세션 종료 저장 성공 뒤 마스킹된 client-visible 내담자 발화만 `app.turn_embedding`에 BGE-M3 dense/sparse로 `ON CONFLICT (turn_id) DO NOTHING` 색인한다. 7차에서는 submit/stream 공통 `_prepare_turn_context()` 경계를 만들고 DB seed recall·pinned fact가 다음 턴 EngineMessage L2/L4에 raw 이름 마스킹 상태로 주입되는 route-level 회귀를 추가했다. 8차에서는 `SessionDigestInput`/`SessionDigestResult`/`SessionSummaryWrite`로 종료 digest 입력·fallback 결과·DB write 인자 경계를 명시했다. future LLM digest worker 입력은 client-visible masked turn과 open thread로 제한하고, 종료 상태 수치·평가 payload·CCD·raw text는 압축 prompt에 넣지 않는다. `/sessions/{id}/end`의 `digest_pending`은 CompressionJob 생성 여부를 알리는 비동기 압축 필요 신호로 유지한다. 새 contradicted fact는 임의 생성하지 않고, 같은 값 재확인은 history를 늘리지 않으며, `locked` fact는 건드리지 않는다. learner-owned `case_profile` 기준 RLS insert/update를 추가했고, 관계갈등·위기·임상 추론은 자동 pinning/모순 처리에서 제외했다. 관계·임상 fact 승격 기준과 LLM digest worker 실행/품질평가/재압축은 후속. **검증: `py -3.11 -X utf8 -m py_compile apps\api\app\services\memory.py apps\api\app\routes\sessions.py apps\api\app\session_persistence.py apps\api\app\test_session_memory.py`, `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_session_memory.py -q` 17 passed, `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` 74 passed, `npm run check:api-types`, `npm run typecheck`.**
|
||||||
- C1 사례개념화 워크시트 3차: `SessionReviewResponse.caseWorksheet`와 리뷰 화면 카드가 탐색 11항목·호소 5영역·인지삼제/감정·보호/방해·생물심리사회 목표 초안을 축어록 근거 기반으로 제공한다. 학습자가 편집한 저장본은 `PUT /sessions/{id}/review/worksheet`로 `app.case_worksheet`에 저장되고, 이후 `GET /review`는 `saved_by_learner` 저장본을 자동 초안보다 우선 반환한다. 2026-06-28에는 `data/rubrics/case-worksheet-rubric.json`, schema, loader/validator service, `scripts/check-case-worksheet-rubric.py`를 추가해 임상팀 확정 루브릭을 외부 JSON으로 받을 scaffold를 만들었다. 현재는 `status=scaffold_only`, `scoring_enabled=false`이며 5개 section/28개 item key와 sha256 provenance만 검증한다. 64차 refactor-governance 패스에서 워크시트 템플릿 key source를 `CASE_WORKSHEET_SECTION_SPECS`/`case_worksheet_template_item_keys()`로 명시해 CLI/test가 더미 턴 없이 같은 생성 spec을 검증하게 했다. 임상팀 확정 루브릭 콘텐츠, AI 추출/채점 적용·calibration, 교수자 승인/반려/수정요청 검수는 후속 gate다. **검증: `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, `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`, 로컬 API smoke 세션 생성→저장→`GET /review` `saved_by_learner:local smoke saved worksheet`.**
|
- C1 사례개념화 워크시트 4차: `SessionReviewResponse.caseWorksheet`와 리뷰 화면 카드가 탐색 11항목·호소 5영역·인지삼제/감정·보호/방해·생물심리사회 목표 초안을 축어록 근거 기반으로 제공한다. 학습자가 편집한 저장본은 `PUT /sessions/{id}/review/worksheet`로 `app.case_worksheet`에 저장되고, 이후 `GET /review`는 `saved_by_learner` 저장본을 자동 초안보다 우선 반환한다. 2026-06-28에는 `data/rubrics/case-worksheet-rubric.json`, schema, loader/validator service, `scripts/check-case-worksheet-rubric.py`를 추가해 임상팀 확정 루브릭을 외부 JSON으로 받을 scaffold를 만들었다. 현재는 `status=scaffold_only`, `scoring_enabled=false`이며 5개 section/28개 item key와 sha256 provenance만 검증한다. 64차 refactor-governance 패스에서 워크시트 템플릿 key source를 `CASE_WORKSHEET_SECTION_SPECS`/`case_worksheet_template_item_keys()`로 명시해 CLI/test가 더미 턴 없이 같은 생성 spec을 검증하게 했다. 73차에서는 기존 `app.session_review_status`에 `worksheet_status`/`worksheet_note`/`worksheet_reviewed_at`을 추가하고, `PUT /teacher/sessions/{id}/review-status`와 교수자 `SessionReview` 카드에서 승인·수정요청·반려 수동 판정을 저장/표시한다. 임상팀 확정 루브릭 콘텐츠, AI 추출/채점 적용·calibration, 승인 후 잠금·재제출 정책, 재귀학습 데이터셋 approved 연계는 후속 gate다. **검증: `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, `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`, `npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1` 3 passed, 로컬 API smoke 세션 생성→저장→`GET /review` `saved_by_learner:local smoke saved worksheet`.**
|
||||||
- C3 이론모드 2차: `theory_mode`가 `TurnContext`/sessions/voice/evaluator뿐 아니라 `build_turn_messages`의 엔진 메시지까지 전달된다. 프론트는 `persona.theory_target` 기준 기본값을 유지하되, 세션 시작 전 `humanistic`/`cbt`/`integrative` segmented control로 학습자가 명시 선택하고 `POST /sessions`의 `theory_mode`로 보낸다. CBT 체인·이론부합 루브릭은 후속. **검증: `python -B -m pytest -p no:cacheprovider app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q` 31 passed, `npm run typecheck`, `npm run build`, `npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --workers=1` 4 passed, `npx playwright test e2e/session-layout.spec.ts --project=chromium-mobile --workers=1` 4 passed, `npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1` 1 passed, `npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1` 9 passed.**
|
- C3 이론모드 2차: `theory_mode`가 `TurnContext`/sessions/voice/evaluator뿐 아니라 `build_turn_messages`의 엔진 메시지까지 전달된다. 프론트는 `persona.theory_target` 기준 기본값을 유지하되, 세션 시작 전 `humanistic`/`cbt`/`integrative` segmented control로 학습자가 명시 선택하고 `POST /sessions`의 `theory_mode`로 보낸다. CBT 체인·이론부합 루브릭은 후속. **검증: `python -B -m pytest -p no:cacheprovider app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q` 31 passed, `npm run typecheck`, `npm run build`, `npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --workers=1` 4 passed, `npx playwright test e2e/session-layout.spec.ts --project=chromium-mobile --workers=1` 4 passed, `npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1` 1 passed, `npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1` 9 passed.**
|
||||||
- M3 인증 claim 1차: Google/SAML/dev-login이 `AUTH_EMAIL_COHORT_MAP`·`AUTH_DOMAIN_COHORT_MAP` 및 SAML cohort claim을 `cohort_ids`로 전달하고, DB `app_user.external_id`는 provider subject(`google:`/`saml:`/`dev:`) 기반으로 저장한다. 운영 SAML 서명검증, 기관 claim schema/test tenant, deprovisioning audit은 후속. **검증: `pytest app/test_auth_providers.py -q` 30 passed, `pytest app/ -q` 178 passed.**
|
- M3 인증 claim 1차: Google/SAML/dev-login이 `AUTH_EMAIL_COHORT_MAP`·`AUTH_DOMAIN_COHORT_MAP` 및 SAML cohort claim을 `cohort_ids`로 전달하고, DB `app_user.external_id`는 provider subject(`google:`/`saml:`/`dev:`) 기반으로 저장한다. 운영 SAML 서명검증, 기관 claim schema/test tenant, deprovisioning audit은 후속. **검증: `pytest app/test_auth_providers.py -q` 30 passed, `pytest app/ -q` 178 passed.**
|
||||||
- X2 예산 경고/저비용 평가 라우팅/evaluator cache 관측·일별 비용 추이·모델별 비용 검증 리포트 2차: `ADMIN_USAGE_BUDGET_USD` 설정값을 기준으로 `GET /admin/usage`가 `budget.status=disabled|ok|warn|exceeded`, 사용률, 잔여 예산을 반환하고 관리자 콘솔이 예산 상태 배너를 표시한다. 80% 이상 warn, 100% 이상 exceeded. `EVALUATOR_FAST_MODEL`/`EVALUATOR_DEEP_MODEL` 설정 시 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달하고, 비워두면 기존 gateway default 라우팅을 유지한다. fast/deep evaluator structured 결과는 canonical request SHA-256 기반 인메모리 semantic cache로 재사용하며, 원문 prompt·completion은 저장하지 않고 성공 파싱 결과만 TTL/entry 제한 안에서 캐시한다. `/admin/usage`와 관리자 `AI 비용` 카드는 cache enabled/entries/hits/misses/stores/evictions/requests/hit_rate와 일별 `daily_cost` 추이를 표시하되 cache key·prompt·completion은 노출하지 않는다. `app.services.usage_report`와 `scripts/report-ai-usage.py`는 동일 usage JSON에서 provider/model별 cost share, token share, cost/turn, cost/1k tokens, metered coverage, budget/cache warning을 산출한다. 자동 차단·한도 enforcement 정책은 후속. **검증: `python -B -m py_compile app/services/usage_report.py app/test_usage_report.py ..\..\scripts\report-ai-usage.py`, `python -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` 42 passed, `scripts/report-ai-usage.py` sample CLI schema `vignette.ai_usage_model_cost_report.v1` 생성, prior `npm run check:api-types`, `npm run typecheck`, `npm run build`, admin E2E evidence remains valid because API response shape was not changed.**
|
- X2 예산 경고/저비용 평가 라우팅/evaluator cache 관측·일별 비용 추이·모델별 비용 검증 리포트 2차: `ADMIN_USAGE_BUDGET_USD` 설정값을 기준으로 `GET /admin/usage`가 `budget.status=disabled|ok|warn|exceeded`, 사용률, 잔여 예산을 반환하고 관리자 콘솔이 예산 상태 배너를 표시한다. 80% 이상 warn, 100% 이상 exceeded. `EVALUATOR_FAST_MODEL`/`EVALUATOR_DEEP_MODEL` 설정 시 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달하고, 비워두면 기존 gateway default 라우팅을 유지한다. fast/deep evaluator structured 결과는 canonical request SHA-256 기반 인메모리 semantic cache로 재사용하며, 원문 prompt·completion은 저장하지 않고 성공 파싱 결과만 TTL/entry 제한 안에서 캐시한다. `/admin/usage`와 관리자 `AI 비용` 카드는 cache enabled/entries/hits/misses/stores/evictions/requests/hit_rate와 일별 `daily_cost` 추이를 표시하되 cache key·prompt·completion은 노출하지 않는다. `app.services.usage_report`와 `scripts/report-ai-usage.py`는 동일 usage JSON에서 provider/model별 cost share, token share, cost/turn, cost/1k tokens, metered coverage, budget/cache warning을 산출한다. 자동 차단·한도 enforcement 정책은 후속. **검증: `python -B -m py_compile app/services/usage_report.py app/test_usage_report.py ..\..\scripts\report-ai-usage.py`, `python -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` 42 passed, `scripts/report-ai-usage.py` sample CLI schema `vignette.ai_usage_model_cost_report.v1` 생성, prior `npm run check:api-types`, `npm run typecheck`, `npm run build`, admin E2E evidence remains valid because API response shape was not changed.**
|
||||||
- H4 LLM call audit/마스킹 2차: 상담 생성(generate/stream)·fast-loop 평가·deep-loop 평가의 외부 LLM 호출 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency metadata만 적재한다. prompt/completion 본문은 저장하지 않고 감사 실패는 상담 루프를 막지 않는다. 2026-06-28에는 한국어 이름/기관 로컬 휴리스틱 마스킹 1차를 추가했고, fast evaluator prompt의 내담자 응답과 client turn `text_masked`도 마스킹본을 쓰게 보강했다. 이어서 `app.services.pii_masking_eval`, `data/privacy/pii-masking-ko-fixtures.json`, `scripts/evaluate-pii-masking.py`로 합성 fixture 15케이스 평가 harness를 추가했고, `data/privacy/pii-masking-eval-input.schema.json`과 `data/privacy/pii-masking-eval-report.schema.json`으로 source/category/severity metadata와 summary-only `technical_dry_run` report 계약을 고정했다. 66차에서는 `제 이름은 김서연입니다`, `보호자 이름은 박민수입니다`, `저는 최하늘입니다` 자연 발화형 이름 라벨·자기소개 케이스와 `이름은 중요하지 않다` negative control을 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 기본 CLI JSON은 `masked_text`/`forbidden_remaining` 원문 증거를 제외하며, `소속`/`안내`/`이름` NAME 오탐도 stopword로 보정했다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log`가 8→11로 3행 증가했다(session `2460d56c-a9cb-4a40-a175-9575d510a5e9`). 공개 Google OAuth 실제 `/turn` proof는 별도 B2 항목에 남긴다. **검증: `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`, `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, 과거 전체 기준 `pytest app/ -q` 119 passed.**
|
- H4 LLM call audit/마스킹 2차: 상담 생성(generate/stream)·fast-loop 평가·deep-loop 평가의 외부 LLM 호출 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency metadata만 적재한다. prompt/completion 본문은 저장하지 않고 감사 실패는 상담 루프를 막지 않는다. 2026-06-28에는 한국어 이름/기관 로컬 휴리스틱 마스킹 1차를 추가했고, fast evaluator prompt의 내담자 응답과 client turn `text_masked`도 마스킹본을 쓰게 보강했다. 이어서 `app.services.pii_masking_eval`, `data/privacy/pii-masking-ko-fixtures.json`, `scripts/evaluate-pii-masking.py`로 합성 fixture 15케이스 평가 harness를 추가했고, `data/privacy/pii-masking-eval-input.schema.json`과 `data/privacy/pii-masking-eval-report.schema.json`으로 source/category/severity metadata와 summary-only `technical_dry_run` report 계약을 고정했다. 66차에서는 `제 이름은 김서연입니다`, `보호자 이름은 박민수입니다`, `저는 최하늘입니다` 자연 발화형 이름 라벨·자기소개 케이스와 `이름은 중요하지 않다` negative control을 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 70차에서는 `guardrail.mask_pii()` 내부에 선택형 한국어 PII recognizer adapter 경계를 추가했다. adapter는 import-time hard dependency가 아니며 명시 등록 전에는 비활성이고, 실패해도 기존 regex fallback이 마지막 안전망으로 유지된다. fake adapter 테스트는 regex가 못 잡는 별명/기관 span을 `[NAME]`/`[ORG]`로 마스킹하고 같은 문장의 전화번호는 후단 regex가 `[PHONE]`으로 처리하는지, adapter 실패 시에도 fallback이 유지되는지 검증한다. span 중복 처리는 문자별 set 할당 대신 정렬된 `last_end` 비교로 정리했다. 기본 CLI JSON은 `masked_text`/`forbidden_remaining` 원문 증거를 제외하며, `소속`/`안내`/`이름` NAME 오탐도 stopword로 보정했다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log`가 8→11로 3행 증가했다(session `2460d56c-a9cb-4a40-a175-9575d510a5e9`). 공개 Google OAuth 실제 `/turn` proof는 별도 B2 항목에 남긴다. **검증: `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`, `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, 과거 전체 기준 `pytest app/ -q` 119 passed.**
|
||||||
- H4 온보딩·동의 게이트 3차: `app_user`에 이름·소속·학과·학년/직위·전화번호·주소/수령지·닉네임·자기소개·아바타 URL·약관/개인정보 동의 시각·버전 필드를 추가했다. 로그인 직후 `/onboarding` 완료 전에는 전역 `OnboardingGate`가 `/onboarding` 외 모든 앱 URL(`/learn`, `/settings`, `/admin`, `/dev/avatar-preview`, `/login` 포함)을 온보딩으로 이동시키고, 온보딩 중에는 공용 셸 메뉴 없이 가입 정보 입력 폼만 보여준다. learner `POST /sessions`와 voice dev persona 시작은 `onboarding_required`를 먼저 확인하고, 온보딩 저장 시 learner `consent_at`도 함께 세팅한다. 아바타는 `/users/me/avatar`에서 PNG/JPG/WebP MIME·시그니처·3MB 제한을 통과한 파일만 `USER_UPLOAD_DIR/profile-avatars`에 저장하고 DB에는 URL만 보관한다. 약관·개인정보 처리방침은 개인정보 보호법·처리방침 작성지침·안전성 확보조치 기준·약관규제법 취지를 반영한 개발 초안으로 `/users/legal-docs`에서 제공한다. 관리자/교수자 권한은 온보딩 화면에서 신청받지 않고 기존 `AUTH_ADMIN_EMAILS`/`AUTH_TEACHER_EMAILS` allowlist 및 관리자 사용자 관리 경로로만 부여한다. 한국어 이름/기관은 로컬 휴리스틱 마스킹과 합성 fixture 평가 harness 1차까지 완료했고, 모델 기반 정밀 ko NER, 실제 운영 말뭉치 기반 오탐/미탐 평가, guardian/legal 최종 서명 동의서 evidence, 공개 Google OAuth 실제 `/turn` proof는 후속. **검증: `pytest app/ -q` 178 passed, `pytest engine_gateway/ -q` 11 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npx playwright test e2e/auth.spec.ts --project=chromium-desktop --workers=1` 7 passed, `PLAYWRIGHT_PORT=5174 npm run e2e` 113 passed.**
|
- H4 온보딩·동의 게이트 3차: `app_user`에 이름·소속·학과·학년/직위·전화번호·주소/수령지·닉네임·자기소개·아바타 URL·약관/개인정보 동의 시각·버전 필드를 추가했다. 로그인 직후 `/onboarding` 완료 전에는 전역 `OnboardingGate`가 `/onboarding` 외 모든 앱 URL(`/learn`, `/settings`, `/admin`, `/dev/avatar-preview`, `/login` 포함)을 온보딩으로 이동시키고, 온보딩 중에는 공용 셸 메뉴 없이 가입 정보 입력 폼만 보여준다. learner `POST /sessions`와 voice dev persona 시작은 `onboarding_required`를 먼저 확인하고, 온보딩 저장 시 learner `consent_at`도 함께 세팅한다. 아바타는 `/users/me/avatar`에서 PNG/JPG/WebP MIME·시그니처·3MB 제한을 통과한 파일만 `USER_UPLOAD_DIR/profile-avatars`에 저장하고 DB에는 URL만 보관한다. 약관·개인정보 처리방침은 개인정보 보호법·처리방침 작성지침·안전성 확보조치 기준·약관규제법 취지를 반영한 개발 초안으로 `/users/legal-docs`에서 제공한다. 관리자/교수자 권한은 온보딩 화면에서 신청받지 않고 기존 `AUTH_ADMIN_EMAILS`/`AUTH_TEACHER_EMAILS` allowlist 및 관리자 사용자 관리 경로로만 부여한다. 한국어 이름/기관은 로컬 휴리스틱 마스킹, optional ko recognizer adapter 배선, 합성 fixture 평가 harness까지 완료했고, 실제 ko recognizer 모델/provider 선정, 운영 말뭉치 기반 오탐/미탐 평가, guardian/legal 최종 서명 동의서 evidence, 공개 Google OAuth 실제 `/turn` proof는 후속. **검증: `pytest app/ -q` 178 passed, `pytest engine_gateway/ -q` 11 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npx playwright test e2e/auth.spec.ts --project=chromium-desktop --workers=1` 7 passed, `PLAYWRIGHT_PORT=5174 npm run e2e` 113 passed.**
|
||||||
- RAG warm 동시성 안정화: layout/session E2E가 여러 세션을 빠르게 만들 때 BGE-M3 embedder가 동시에 여러 번 지연 로드되며 `tqdm` lock 예외와 API health/dev-login timeout이 반복됐다. `rag.py`에서 embedder load/encode를 process-wide `RLock`으로 직렬화하고, `sessions.py`의 `_warm_rag_caches`를 semaphore 1개로 제한해 warm task가 상담 요청 경로를 막지 않도록 했다. **검증: `pytest app/ -q` 125 passed, `npx playwright test e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts --project=chromium-single-run --project=chromium-desktop --workers=1` 11 passed.**
|
- RAG warm 동시성 안정화: layout/session E2E가 여러 세션을 빠르게 만들 때 BGE-M3 embedder가 동시에 여러 번 지연 로드되며 `tqdm` lock 예외와 API health/dev-login timeout이 반복됐다. `rag.py`에서 embedder load/encode를 process-wide `RLock`으로 직렬화하고, `sessions.py`의 `_warm_rag_caches`를 semaphore 1개로 제한해 warm task가 상담 요청 경로를 막지 않도록 했다. **검증: `pytest app/ -q` 125 passed, `npx playwright test e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts --project=chromium-single-run --project=chromium-desktop --workers=1` 11 passed.**
|
||||||
- 공개/로컬/Tailnet 로그인 복구: public API 530 원인은 prod에서 개발 전용 `VIGNETTE_VOICE_POC_SAMPLE_TTS=true`가 fail-close된 것과 DB `app.admin_engine_config` 기본 행 부재였다. 2026-06-27 모바일 502는 cloudflared 로그의 `127.0.0.1:8001` origin refused와 일치했다. `scripts/start-public-runtime.ps1`는 public prod 기동 시 샘플 TTS를 강제로 끄고, 운영 DB에는 `claude_cli`/`127.0.0.1:9099` engine config 행을 복구했다. 추가로 dev/Tailnet에서는 public OAuth callback이 로컬/Tailnet 세션으로 붙지 않으므로 Google 버튼과 직접 시작 URL을 `local_oauth_unavailable`로 차단하고 dev-login만 사용한다. public OAuth state는 HttpOnly 쿠키에 묶인 HMAC 서명 토큰으로 복구해 API 재시작 뒤 콜백이 `invalid_state`로 떨어지지 않는다. callback 실패는 비밀값 없는 reason/status로 남기고, 로그인 화면도 token/state/provider/identity 실패 메시지와 reason code를 분리한다. 2026-06-27 18:57 KST에 Tailnet stale Vite `allowedHosts` 재발 상태를 재확인했고, `alpaca-home.taile93291.ts.net`를 Vite 기본 허용 host에도 포함해 수동 Vite 기동 시 403 재발 가능성을 낮췄다. **검증: `https://vignette.chanpaca.net/login` 200, `https://api-vignette.chanpaca.net/health` prod/db/engine true, public Google auth redirect 302 + HttpOnly state cookie, provider callback error → `/login?oauth=access_denied`, `https://alpaca-home.taile93291.ts.net/login` 200 + `/api/health` dev/db/engine true + `/api/auth/config` 200, env 없는 임시 Vite host-header smoke 200, local/Tailnet Google direct는 `local_oauth_unavailable`, local auth E2E 7 passed, Tailnet auth/dev-login E2E 2 passed.**
|
- 공개/로컬/Tailnet 로그인 복구: public API 530 원인은 prod에서 개발 전용 `VIGNETTE_VOICE_POC_SAMPLE_TTS=true`가 fail-close된 것과 DB `app.admin_engine_config` 기본 행 부재였다. 2026-06-27 모바일 502는 cloudflared 로그의 `127.0.0.1:8001` origin refused와 일치했다. `scripts/start-public-runtime.ps1`는 public prod 기동 시 샘플 TTS를 강제로 끄고, 운영 DB에는 `claude_cli`/`127.0.0.1:9099` engine config 행을 복구했다. 추가로 dev/Tailnet에서는 public OAuth callback이 로컬/Tailnet 세션으로 붙지 않으므로 Google 버튼과 직접 시작 URL을 `local_oauth_unavailable`로 차단하고 dev-login만 사용한다. public OAuth state는 HttpOnly 쿠키에 묶인 HMAC 서명 토큰으로 복구해 API 재시작 뒤 콜백이 `invalid_state`로 떨어지지 않는다. callback 실패는 비밀값 없는 reason/status로 남기고, 로그인 화면도 token/state/provider/identity 실패 메시지와 reason code를 분리한다. 2026-06-27 18:57 KST에 Tailnet stale Vite `allowedHosts` 재발 상태를 재확인했고, `alpaca-home.taile93291.ts.net`를 Vite 기본 허용 host에도 포함해 수동 Vite 기동 시 403 재발 가능성을 낮췄다. **검증: `https://vignette.chanpaca.net/login` 200, `https://api-vignette.chanpaca.net/health` prod/db/engine true, public Google auth redirect 302 + HttpOnly state cookie, provider callback error → `/login?oauth=access_denied`, `https://alpaca-home.taile93291.ts.net/login` 200 + `/api/health` dev/db/engine true + `/api/auth/config` 200, env 없는 임시 Vite host-header smoke 200, local/Tailnet Google direct는 `local_oauth_unavailable`, local auth E2E 7 passed, Tailnet auth/dev-login E2E 2 passed.**
|
||||||
- Docker dev DB 안전장치: `scripts/dev-up.ps1`가 `vignette-dev-db` healthcheck/pg_isready/role safety를 점검하고, 새 컨테이너 생성 시 `vignette_owner`와 앱 role을 분리해 healthcheck를 붙인다. `infra\.env` compose 필수값 누락도 사전 경고한다. `scripts/dev-down.ps1`은 기본 DB 보존, `-Db` 지정 시 컨테이너 중지로 정리했다. **검증: 현재 Docker DB accepting connections, `vignette_app:false:false`, 기존 컨테이너 healthcheck 없음. 패키징 smoke에서 fresh compose DB는 owner/app role 분리 상태로 API healthy까지 통과했다. 기존 dev DB는 `POSTGRES_USER=vignette` 기반이라 운영 role 전환은 별도 owner migration 확인 후 진행한다.**
|
- Docker dev DB 안전장치: `scripts/dev-up.ps1`가 `vignette-dev-db` healthcheck/pg_isready/role safety를 점검하고, 새 컨테이너 생성 시 `vignette_owner`와 앱 role을 분리해 healthcheck를 붙인다. `infra\.env` compose 필수값 누락도 사전 경고한다. `scripts/dev-down.ps1`은 기본 DB 보존, `-Db` 지정 시 컨테이너 중지로 정리했다. **검증: 현재 Docker DB accepting connections, `vignette_app:false:false`, 기존 컨테이너 healthcheck 없음. 패키징 smoke에서 fresh compose DB는 owner/app role 분리 상태로 API healthy까지 통과했다. 기존 dev DB는 `POSTGRES_USER=vignette` 기반이라 운영 role 전환은 별도 owner migration 확인 후 진행한다.**
|
||||||
|
|
|
||||||
113
docs/ops/dark-ui-refresh-implementation-plan-2026-06-28.md
Normal file
113
docs/ops/dark-ui-refresh-implementation-plan-2026-06-28.md
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# 다크 UI v2 실행 준비 계획 — 2026-06-28
|
||||||
|
|
||||||
|
목표: `docs/design-concepts/generated/*-v2-dark-unified.png` 시안을 실제 코드 기반 UI 개선 작업으로 전환한다. 생성 이미지는 방향성 기준일 뿐이며, 제품 UI는 React/CSS 컴포넌트로 구현한다.
|
||||||
|
|
||||||
|
## 0. 기준 자산
|
||||||
|
|
||||||
|
| 화면 | 기준 이미지 | 구현 기준 |
|
||||||
|
|---|---|---|
|
||||||
|
| 학습자 홈 | `docs/design-concepts/generated/02-learner-home-responsive-v2-dark-unified.png` | `apps/web/src/pages/LearnerHome.tsx`, `apps/web/src/pages/learner-home.css` |
|
||||||
|
| 라이브 상담 세션 | `docs/design-concepts/generated/03-session-responsive-v2-dark-unified.png` | `apps/web/src/pages/Session.tsx`, `apps/web/src/pages/session/session.css` |
|
||||||
|
| 회기 리뷰 | `docs/design-concepts/generated/04-session-review-responsive-v2-dark-unified.png` | `apps/web/src/pages/SessionReview.tsx`, `apps/web/src/pages/session-review/session-review.css` |
|
||||||
|
| 토큰 | `apps/web/src/styles/tokens.css` | dark 기본, sage-teal/clay/warn 배급 유지 |
|
||||||
|
| 프롬프트 근거 | `docs/design-concepts/prompts/vignette-dark-ui-refresh-2026-06-28.md` | UX 원칙과 금지 패턴 |
|
||||||
|
|
||||||
|
## 1. 적용 원칙
|
||||||
|
|
||||||
|
- 기존 기능을 새 이미지처럼 보이게 숨기지 않는다. 실제 API·세션 상태·저장된 리뷰·코칭 이력만 표시한다.
|
||||||
|
- generated PNG를 앱에 직접 박지 않는다. 이미지는 레이아웃/밀도/톤 기준으로만 쓴다.
|
||||||
|
- active 작업 화면은 단일 dark surface로 유지한다. 큰 white/beige 카드, 라이트 패널, 장식용 hero는 금지한다.
|
||||||
|
- 실시간 상담 중 피드백은 주변 신호다. 중앙 과업은 내담자 얼굴, 자막, 입력이다.
|
||||||
|
- 회기 리뷰는 점수 폭격이 아니라 근거 기반 회고다. 축어록·근거·워크시트 연결을 우선한다.
|
||||||
|
- 학습자 홈은 create/continue-first flow다. 첫 화면의 핵심 결정은 새 회기 시작 또는 이어가기다.
|
||||||
|
|
||||||
|
## 2. 구현 순서
|
||||||
|
|
||||||
|
### P1. 라이브 상담 세션
|
||||||
|
|
||||||
|
우선순위가 가장 높다. 사용자가 지적한 light/dark 혼합 문제가 여기서 시작됐고, 현재 학습 몰입도에 직접 영향을 준다.
|
||||||
|
|
||||||
|
변경 후보:
|
||||||
|
|
||||||
|
- `sx-page--active`, `sx-panel`, `sx-transcript`, `sx-controlbar`의 dark surface 명도 차이를 v2 기준으로 정리한다.
|
||||||
|
- 좌측 `내담자 컨텍스트`와 `회기 진행`은 더 조밀하게 만들되, 단계/주호소/난도/접근 정보는 유지한다.
|
||||||
|
- 중앙 stage는 avatar/orb/현재 상태 문구 위계를 분명히 하고, transcript와 시각적으로 같은 dark 작업면으로 맞춘다.
|
||||||
|
- 우측 rail은 `라이브 신호`, `관찰 신호`, `안전 점검`의 순서를 유지하되 색을 낮추고 amber/red 남용을 막는다.
|
||||||
|
- mobile/tablet에서는 context strip, stage, transcript, input, controlbar가 한 흐름으로 내려오게 한다.
|
||||||
|
|
||||||
|
검증:
|
||||||
|
|
||||||
|
- `cd apps/web; npm run typecheck`
|
||||||
|
- `cd apps/web; npm run build`
|
||||||
|
- `cd apps/web; npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1`
|
||||||
|
- `cd apps/web; npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1`
|
||||||
|
|
||||||
|
완료 증거:
|
||||||
|
|
||||||
|
- `docs/design-verification/full-pages/03-session-active-desktop.png`
|
||||||
|
- `docs/design-verification/full-pages/03-session-active-tablet.png`
|
||||||
|
- `docs/design-verification/full-pages/03-session-active-mobile.png`
|
||||||
|
- 가로 overflow 0, 컨트롤/텍스트 클립 0, 큰 white CSS surface 0.
|
||||||
|
|
||||||
|
### P2. 회기 리뷰
|
||||||
|
|
||||||
|
세션 다음의 학습 전환 화면이다. v2처럼 dark review workbench로 톤을 맞추되, 빈 상태와 실제 저장본 상태를 분리해야 한다.
|
||||||
|
|
||||||
|
변경 후보:
|
||||||
|
|
||||||
|
- filled review는 transcript 중심 + insight rail + worksheet workbench 구조를 강화한다.
|
||||||
|
- empty review는 가짜 데이터 없이 `대기` 상태만 dark panel로 표현한다.
|
||||||
|
- audio replay, PDF export, supervisor/teacher review, worksheet manual review 상태를 한눈에 보이게 정리한다.
|
||||||
|
- mobile은 `요약 / 트랜스크립트 / 인사이트` 식의 progressive disclosure를 유지한다.
|
||||||
|
|
||||||
|
검증:
|
||||||
|
|
||||||
|
- `cd apps/web; npm run typecheck`
|
||||||
|
- `cd apps/web; npm run build`
|
||||||
|
- `cd apps/web; npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1`
|
||||||
|
- `cd apps/web; npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1`
|
||||||
|
|
||||||
|
완료 증거:
|
||||||
|
|
||||||
|
- filled/empty review 모두 dark surface 유지.
|
||||||
|
- worksheet, transcript, feedback cards가 서로 겹치지 않는다.
|
||||||
|
- 교사용 read-only 상태와 학습자 편집 상태가 깨지지 않는다.
|
||||||
|
|
||||||
|
### P3. 학습자 홈
|
||||||
|
|
||||||
|
세션 시작 전 의사결정 화면이다. 현재 구현을 유지하면서 persona 선택과 이어하기 CTA를 더 명확하게 만든다.
|
||||||
|
|
||||||
|
변경 후보:
|
||||||
|
|
||||||
|
- persona list는 코드, 이름, 나이/상황, 난도, 접근을 한 행에서 비교 가능하게 유지한다.
|
||||||
|
- selected-client workspace는 주호소, 현재 회기, 마지막 반응, `이어하기`/`새 회기 시작` CTA를 분명히 둔다.
|
||||||
|
- 기존 회기, 리뷰 대기, 다음 연습 추천은 실제 데이터 기반으로만 표시한다.
|
||||||
|
- mobile bottom nav와 compact card의 텍스트 클립을 다시 확인한다.
|
||||||
|
|
||||||
|
검증:
|
||||||
|
|
||||||
|
- `cd apps/web; npm run typecheck`
|
||||||
|
- `cd apps/web; npm run build`
|
||||||
|
- `cd apps/web; npx playwright test e2e/learner.spec.ts --project=chromium-desktop --workers=1`
|
||||||
|
- `cd apps/web; npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1`
|
||||||
|
|
||||||
|
완료 증거:
|
||||||
|
|
||||||
|
- create/continue primary action이 첫 화면에서 명확하다.
|
||||||
|
- 390/720/900/1440 폭에서 persona row, CTA, recent session row 텍스트가 잘리지 않는다.
|
||||||
|
|
||||||
|
## 3. 구현 전 체크
|
||||||
|
|
||||||
|
- 현재 작업트리가 dirty 상태이므로 UI 구현 전에는 변경 파일 범위를 좁힌다.
|
||||||
|
- `docs/dev_dashboard.html`에는 PLAN 상태로만 두고, 실제 구현/검증 전까지 DONE으로 바꾸지 않는다.
|
||||||
|
- API response shape를 바꾸지 않는 UI/CSS 라운드로 제한한다. 만약 DTO 변경이 생기면 `npm run check:api-types`를 추가한다.
|
||||||
|
- visual gate가 기존 9개 화면을 모두 보므로 한 화면 수정 후에도 전체 gate를 돌린다.
|
||||||
|
- 새 이미지와 실제 코드가 다를 때는 코드의 실제 기능을 우선한다. 이미지는 방향성이고 기능은 SSOT다.
|
||||||
|
|
||||||
|
## 4. 다음 실행 단위
|
||||||
|
|
||||||
|
1. `P1 라이브 상담 세션`만 먼저 구현한다.
|
||||||
|
2. 현재 `03-session-active-*` 캡처와 v2 이미지를 나란히 비교해 CSS delta를 최소화한다.
|
||||||
|
3. `Session.tsx` 구조 변경은 피하고, 가능한 한 `session.css`에서 surface/density/responsive만 조정한다.
|
||||||
|
4. 검증 통과 후 새 full-page 캡처를 남기고 SSOT를 DONE/VERIFY 상태로 갱신한다.
|
||||||
|
5. 그 다음 `P2 회기 리뷰`, `P3 학습자 홈` 순서로 반복한다.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue