세션 평가와 교수자 분석 보강
This commit is contained in:
parent
5c4ac04e06
commit
fe2796f05a
51 changed files with 4928 additions and 240 deletions
|
|
@ -8,6 +8,26 @@
|
|||
|
||||
최신 동기화 추가(2026-06-29): 관리자·교수자 메일링 시스템 1차가 추가됐다. 가입 승인 요청과 회기 검토 요청은 `app.notification_event`/`app.notification_delivery`에 idempotent 큐로 남기고, SMTP 설정이 있을 때만 실제 메일을 발송한다. 메일 본문은 Vignette 톤앤매너를 따르되 축어록·평가 전문을 포함하지 않고 `/admin/users`, `/teach/session/:sessionId/review` 딥링크만 제공한다.
|
||||
|
||||
최신 동기화 추가(2026-06-30): 기본 역할이 learner로 복원되는 슈퍼관리자 계정이 타 학습자 회기 리뷰 딥링크에서 `session does not belong to user` 403에 막히던 문제를 수정했다. 리뷰 조회는 본인 학습자 접근을 우선 유지하고, 실패 시 슈퍼관리자만 admin 감독자 읽기로 재시도한다. 프론트는 동일 403뿐 아니라 이미 `teacherReview`가 포함된 성공 응답도 감독자 권한 사용자에 한해 `/teach/session/:sessionId/review`로 정규화한다. 일반 learner 및 단순 `admin_access` 위임 계정의 타인 세션 접근 차단은 유지했다. 검증: `app/test_rbac_idor.py` 11 passed, RBAC/auth/teacher focused 47 passed, supervisor route E2E 2 passed(`PLAYWRIGHT_PORT=5176`), `npm run typecheck`, `npm run build`.
|
||||
|
||||
최신 동기화 추가(2026-06-30): `docs/dev_dashboard.html` 자체를 레드팀 검증 대상으로 추가했다. `scripts/test_dev_dashboard_ssot.py`는 SSOT checker import/stateless/owner parser를 unit으로 검증하고, `apps/web/e2e/dev-dashboard.spec.ts`는 local file, `127.0.0.1`, `localhost` origin에서 필터 오순서·빠른 재시도·키보드·details/tabs·hash anchor·링크/이미지 무결성·desktop/mobile overflow를 E2E로 확인한다. 같은 라운드의 라이브 점검에서 public API `https://api-vignette.chanpaca.net/health`와 `/personas`가 Cloudflare 530/error code 1033으로 실패하는 것을 발견했다. 원인은 Docker Desktop 중단으로 `vignette-dev-db`가 꺼진 상태였고, Docker Desktop/DB 재기동 뒤 `scripts/start-public-runtime.ps1`를 다시 실행해 public health 200, unauth `/personas` 401, auth config 200, Google login start 302, watchdog healthy: engine, api, web-preview, cloudflared, public-api까지 복구했다. 검증: dev-dashboard E2E redteam 10 passed, SSOT unit 3 OK, dashboard checker PASS.
|
||||
|
||||
최신 동기화 추가(2026-07-01): 하린/P6 종료 회기의 `SUPERVISOR AI 평가 실패`는 평가가 실행되지 않은 문제가 아니라 102분/138턴 축어록에 대한 회기말 평가 background task가 기존 45초 하드캡에서 timeout된 뒤 `TimeoutError`의 빈 문자열이 그대로 저장된 문제였다. 회기말 평가는 `SESSION_EVALUATION_TIMEOUT` 또는 `ENGINE_TIMEOUT`을 사용하고, 빈 예외 문자열은 타입/명시 메시지로 저장한다. 교수자 재평가 API는 실패 결과를 저장한 뒤 502/503으로 노출하고, 프론트는 종료 실패 alert, 평가 대기 polling, 교수자 `AI 평가 재시도` 버튼을 제공한다. 재시도 실패가 UI에서 조용히 사라지지 않고 기존 실패 리뷰와 새 실패 사유를 유지하는지, 저장된 평가 실패 row가 read-model에서 `평가 실패`와 사유로 노출되는지도 회귀 테스트로 고정했다. 모바일 코칭 모드에서 우측 컬럼 숨김 때문에 AI 튜터 카드가 보이지 않던 회귀도 수정했다. 검증: `python -X utf8 -m pytest app/ -q` 297 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `session-review` desktop/mobile 16 passed, `session-mvp` desktop/mobile/single-run 5 passed, `session-layout` desktop/mobile 8 passed, `layout-visual-gate` 9 passed.
|
||||
|
||||
최신 동기화 추가(2026-07-01): 실제 브라우저 SSE stream 저장 경로를 E2E로 추가 검증했다. 기존 `session-persistence.spec.ts`는 real API/DB를 쓰지만 `/api/sessions/{id}/turn` 직접 호출이라 UI의 `openSessionStream()` → `/sessions/{id}/stream` → 저장 finalize 경로를 타지 않았다. 새 `@single-run` 테스트는 `db=true`, `engine=true` health 조건에서 Session UI로 회기 시작, 학습자 발화 전송, stream 완료 대기, DB-backed `/review` learner/client 축어록 확인까지 수행한다. 이어서 AI 튜터 코칭 카드가 생성만 되고 `app.live_coach_events` 저장 실패가 묻히는 silent failure를 막기 위해 `POST /sessions/{id}/live-coach`의 저장 실패를 503으로 표면화하고, 실제 UI 코칭 모드 → DB-backed `/live-coach` 이력 → 재로딩 후 `C` 마커 유지까지 E2E로 고정했다. crisis `safety`/`done.conversation_stopped` 또는 음성 `reply.conversation_stopped`도 빈 응답으로 일반화하지 않고 109 안전 게이트를 유지하도록 수정했다. 검증: `python -X utf8 -m pytest -p no:cacheprovider app/test_session_turn_persistence.py -q` 27 passed, `PLAYWRIGHT_PORT=5193 npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1` 2 passed, 최신 `PLAYWRIGHT_PORT=5202 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` 6 passed, `npm run typecheck`, `npx playwright test --list` 현재 160 tests / 18 files.
|
||||
|
||||
최신 동기화 추가(2026-07-01): 음성 비언어 메타데이터도 mock/fixture 전용 검증에 머물던 구간을 좁혔다. `db.healthcheck()`, runtime table readiness, deploy preflight DB mode가 `app.turns.provider_events` 하나가 아니라 `audio_ref`/`silence_ms`/`speech_rate`/`barge_in`/`provider_events` 5개 컬럼 전체를 확인한다. dev/runtime bootstrap은 누락된 nullable 음성 컬럼도 보강한다. `session-persistence.spec.ts`에는 실제 DB 회기를 만들고 `/api/voice/ws` `stt_result` control 및 실제 Session 마이크 UI 생성 메타를 DB-backed `/review` 제한 `nonverbal` 칩까지 검증하는 `@single-run` E2E를 추가했다. 검증: `python -X utf8 -m pytest -p no:cacheprovider app/test_runtime_policy.py app/test_session_turn_persistence.py app/test_voice_ws.py app/test_voice_service.py -q` 77 passed, `PLAYWRIGHT_PORT=5199 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` 5 passed, `python -X utf8 -m py_compile app/db.py app/auth_sessions.py app/test_runtime_policy.py ..\..\scripts\check-deploy-preflight.py`, `python -X utf8 scripts\check-deploy-preflight.py --skip-db --env-file infra\.env.example --allow-placeholder-secrets`.
|
||||
|
||||
최신 동기화 추가(2026-07-01): 실제 Session 마이크 UI 경로도 음성 비언어 메타를 생성하도록 보강했다. `AudioWorklet` 캡처는 PCM RMS/peak 기반으로 voice activity와 trailing silence를 추정하고 `audio_end`에 `silence_ms`, `barge_in=false`, `provider_events`(`voice_activity`, `silence`)를 싣는다. `MediaRecorder` fallback은 raw PCM을 볼 수 없으므로 녹음 duration 기반 voice activity만 보낸다. 리뷰 read-model은 turn-level `silence_ms`가 이미 침묵 칩을 만들면 provider `silence` 칩을 중복 노출하지 않는다. `voice-success.spec.ts`는 실제 마이크 버튼 경로가 `audio_end` 메타를 보내는지 확인하고, `session-persistence.spec.ts`는 그 UI 생성 메타가 DB-backed `/review` `nonverbal`까지 이어지는지 확인한다. 검증: `npm run typecheck`, `python -X utf8 -m pytest -p no:cacheprovider app/test_runtime_policy.py app/test_session_turn_persistence.py app/test_voice_ws.py app/test_voice_service.py -q` 77 passed, `PLAYWRIGHT_PORT=5197 npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run --workers=1` 2 passed, 최신 `PLAYWRIGHT_PORT=5202 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` 6 passed, `npx playwright test --list` 현재 160 tests / 18 files.
|
||||
|
||||
최신 동기화 추가(2026-07-01): 회기 종료 후 background deep 평가가 실제 durable DB row로 저장되는지 fixture 없이 검증했다. `_schedule_session_evaluation()`은 생성 task를 반환하고 done callback에서 crash/cancel을 관찰하며, 평가 결과·timeout/error 저장이 durable store에 닿지 않으면 명시 로그를 남긴다. 교수자 명시 재평가 API는 평가 산출물이 생성됐지만 저장 실패하면 503으로 노출하고, `GET /eval/sessions/{id}/evaluation`은 `status/error/durable`을 제공해 캐시 복원과 DB 저장 증거를 구분한다. `session-persistence.spec.ts`는 학습자 세션 생성→턴→종료→teacher 로그인→`/eval/.../evaluation` polling→교수자 리뷰 UI `평가 완료`까지 실제 DB/API/엔진으로 확인한다. 검증: `python -X utf8 -m pytest -p no:cacheprovider app/ -q` 309 passed, `npm run check:api-types`, `npm run typecheck`, `PLAYWRIGHT_PORT=5205 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` 7 passed, `PLAYWRIGHT_PORT=5203 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1` 16 passed, `npx playwright test --list` 현재 162 tests / 18 files.
|
||||
|
||||
최신 동기화 추가(2026-07-01): 교수자 회기 리뷰 데스크톱에서 `overview`/`transcript`가 여러 CSS Grid row를 가로질러 오른쪽 검토 rail 높이를 나눠 먹으며 중앙에 큰 빈 row가 생기던 문제를 제거했다. 1181px 이상 교수자 리뷰는 main column + review rail 2열 wrapper로 분리하고, main 내부는 요약 전체폭, 차트/회기 흐름 2열, 축어록 전체폭으로 배치한다. 회귀 방지로 `layout-visual-gate.spec.ts`에 교수자 리뷰 폭별 dead vertical gap 검증을 추가했다. 검증: `npm run typecheck`, `npm run build`, `npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1` 8 passed, `npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1` 10 passed, `npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1` 8 passed.
|
||||
|
||||
최신 동기화 추가(2026-07-01): 교수자 학생 분석을 콘솔에서 분리했다. `GET /teacher/learners/{learner_id}/analysis`는 담당 범위 안의 특정 학습자 전체 회기를 오래된 순서로 반환하고, 제한 없는 회기별 추이 point, 라포·탐색·개입·정리 단계 분포, 검토 대기/완료 카운트를 함께 내려준다. `/teach`는 검토 큐·위기 알림·페르소나 검수·최근 회기 triage만 맡고, 좌측 메뉴의 `/teach/analysis`가 학습자 목록 레일 → 사용자별 분석 → 전체 회기 타임라인 → 회기 리뷰 드릴다운을 맡는다. 검증: `python -X utf8 -m pytest -p no:cacheprovider app/test_teacher_dashboard.py -q` 5 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `PLAYWRIGHT_PORT=5211 npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1` 9 passed, `PLAYWRIGHT_PORT=5214 npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1` 11 passed, `python -X utf8 scripts/check-dev-dashboard-ssot.py --json` PASS, `npx playwright test --list` 현재 163 tests / 18 files.
|
||||
|
||||
최신 동기화 추가(2026-07-01): 원천문서 갭 E2E 탐색을 이어서 H1/C3 silent failure 후보를 좁혔다. `session-persistence.spec.ts`는 Phase 3 pre/post 3척도 입력이 브라우저 리뷰 UI → `PUT /users/me/prepost-measures` → DB-backed `GET /users/me/prepost-measures` → 새로고침 후 UI 복원까지 실제 API/DB로 이어지는지 검증한다. C3 수동 재평가는 persona 기본 `theory_target`이 학습자가 선택한 session `theory_mode`를 덮어쓰던 우선순위 오류를 수정했고, turn 재평가도 `TurnContext.theory_mode`를 채우도록 회귀화했다. 서브에이전트 병렬 조사 결과 남은 큰 공백은 C1 워크시트 learner-save/teacher-review full browser+API+DB E2E, C2 safety_events→교수자 대시보드 E2E, H2 live-coach evidence modal/source-pack actual sync proof다. 검증: `python -X utf8 -m pytest -p no:cacheprovider app/test_eval_routes.py app/test_notifications.py app/test_session_turn_persistence.py -q` 39 passed, `PLAYWRIGHT_PORT=5205 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` 7 passed, `npx playwright test --list` 현재 162 tests / 18 files.
|
||||
|
||||
분류: **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, 교수자 수동 검수 상태 저장까지 4차 구조를 만들었고, C2 위기개입 프로토콜은 1차 구조, C3 이론모드는 2차 명시 선택 UI까지 만들었다. 콘텐츠 정의는 임상팀(구훈정·어유경) 소유라 코드는 구조를 선제 구축하되 임상 문안과 평가기준은 외부 정의로 받는다.
|
||||
|
|
@ -27,6 +47,11 @@
|
|||
- [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] **다크 UI v2 시안 실제 적용 라운드** — (2026-06-29 처리) `02/03/04-*-v2-dark-unified.png` 시안을 실제 React/CSS 화면으로 반영했다. P1 라이브 상담 세션은 내담자 무대 발화, 좌측 세션 진행, 우측 라이브 신호/안전 점검을 다크 훈련 화면으로 재구성했다. P2 회기 리뷰는 learner filled 상태를 3열 workbench(요약/축어록/평가 rail + 하단 워크시트)로 바꾸고 empty review는 sparse third column 없이 유지했다. P3 학습자 홈은 대시보드에서 연습 대상 레일을 제거하고, 진행 회기/리뷰 대기/최근 평가/라포 흐름을 상단 CTA 바로 올렸으며, 오늘 이어갈 회기·AI 코치·다음 연습 추천·최근 피드백을 하나의 `lh-work-cluster` 작업 묶음으로 재배치했다. 오른쪽 레일은 최근 기록, 리뷰 대기, 반복 대상만 담당한다. 생성 PNG는 앱에 직접 넣지 않고 레이아웃·밀도·톤 기준으로만 사용했다. 새 실제 렌더 캡처: `docs/design-verification/full-pages/02-learner-home-{desktop,tablet,mobile}.png`, `03-session-active-{desktop,tablet,mobile}.png`, `04-session-review-{desktop,tablet,mobile}.png`, `04-session-review-empty-{desktop,tablet,mobile}.png`. 실행 기록은 `docs/ops/dark-ui-refresh-implementation-plan-2026-06-28.md`에 고정했다. **최신 검증: `npm run typecheck`, `npm run build`, `npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1` 9 passed.**
|
||||
- [x] **세션 종료 평가 실패 복구 UX + 원인 저장** — (2026-07-01 처리) 긴 회기 종료 후 AI 평가가 45초 timeout으로 실패하면서 `error=""`만 저장되던 silent failure를 제거했다. 종료 실패는 리뷰 화면으로 넘어가지 않고 alert로 표시하며, 종료 후 리뷰는 `reviewReady=false` 상태에서 대기 polling으로 ready 전환을 기다린다. 교수자 검토 화면은 평가 실패/미준비 상태에서 `AI 평가 재시도` 버튼으로 `/eval/sessions/{id}/reevaluate`를 호출한다. 재시도 API는 실패 결과도 DB에 저장한 뒤 non-2xx로 노출한다. 재시도 실패 UI 유지와 저장된 평가 실패 read-model 노출도 회귀 테스트로 고정했다. background task crash 관찰과 저장 실패 표면화, `status/error/durable` 평가 조회, 실제 종료 후 durable DB 평가 row 생성 E2E까지 추가했다. 모바일 코칭 모드의 AI 튜터 카드 숨김 회귀도 함께 수정했다. **검증: `python -X utf8 -m pytest -p no:cacheprovider app/ -q` 309 passed, `npm run check:api-types`, `npm run typecheck`, `PLAYWRIGHT_PORT=5205 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` 7 passed, `PLAYWRIGHT_PORT=5203 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1` 16 passed, 기존 `session-mvp`/layout focused gates 유지.**
|
||||
- [x] **브라우저 SSE stream 저장 경로 회귀** — (2026-07-01 처리) `session-mvp` mock과 직접 `/turn` API 테스트 사이에 있던 실제 UI stream 저장 공백을 줄였다. `session-persistence.spec.ts`에 브라우저 Session UI가 실제 `POST /sessions/{id}/stream`을 호출하고, stream 완료 뒤 DB-backed `/review`에서 학습자/내담자 축어록이 반환되는지 검증하는 `@single-run` 테스트를 추가했다. 같은 파일에 AI 튜터 코칭 이력 저장/재로딩 회귀와 음성 비언어 메타 리뷰 회귀도 추가했다. **검증: `PLAYWRIGHT_PORT=5199 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` 5 passed, `npm run typecheck`.**
|
||||
- [x] **AI 튜터 코칭 이력 저장 실패 표면화** — (2026-07-01 처리) `POST /sessions/{id}/live-coach`가 코칭 제안 생성 후 `save_live_coach_event()` 실패를 `except/pass`로 묻어 카드만 보이고 이력/재로딩에는 사라질 수 있던 silent failure를 제거했다. 저장 계층의 prod fail-closed 503을 그대로 사용자/프론트에 노출하고, DB-backed `/live-coach` 이력과 학습자 발화 `C` 마커가 재로딩 뒤 유지되는지 E2E로 고정했다. **검증: `python -X utf8 -m pytest -p no:cacheprovider app/test_session_turn_persistence.py -k live_coach -q` 4 passed, `PLAYWRIGHT_PORT=5199 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` 5 passed.**
|
||||
- [x] **음성 비언어 메타 DB readiness + 리뷰 E2E** — (2026-07-01 처리) `/health`, runtime table readiness, deploy preflight가 `app.turns` 음성 메타 5개 컬럼 전체를 확인하게 했다. `session-persistence.spec.ts`는 DB-backed voice WS `stt_result`와 실제 Session 마이크 UI 생성 메타 → `/review` `nonverbal` 파생 칩까지 고정한다. 실제 provider 기반 한숨·울음 감지와 물리 마이크 장시간 WSS 실측은 계속 후속 gate다. **검증: backend focused 77 passed, `PLAYWRIGHT_PORT=5197 npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run --workers=1` 2 passed, `PLAYWRIGHT_PORT=5199 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` 5 passed, preflight skip-db PASS.**
|
||||
- [x] **위기 안전 게이트 UI 덮어쓰기 방지** — (2026-07-01 처리) SSE/음성 위기 종료가 빈 client reply로 끝날 때 `내담자 응답 없음` 신호와 live-coach 호출이 안전 게이트를 덮던 경로를 막았다. `conversation_stopped`가 있으면 109 안전 자원, 입력 disabled, 위기 신호를 유지하고 코칭 요청을 보내지 않는다. **검증: `PLAYWRIGHT_PORT=5193 npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1` 2 passed, `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] **상시 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`.**
|
||||
|
|
@ -99,10 +124,10 @@
|
|||
- 학습자 개인화 대시보드 1차: 교수자 성장 집계와 중복되던 점수/라포/기법 계산을 `app.services.session_metrics`로 공용화하고, `GET /sessions/dashboard`가 본인 세션 기반 `overview/growth/persona_progress/achievements/recent_feedback`를 반환한다. 학습자 홈은 누적 회기, 리뷰 대기, 최근 평가, 라포 흐름, 페르소나별 진행, 최근 피드백, 마지막 세션 리캡을 실제 서버 데이터로 표시한다. 성취는 공식 등급/수료가 아니라 실제 연습 milestone만 사용한다. 2026-06-29 refactor-governance 패스에서 `session_metrics.turn_technique_label()`이 DB 재수화 계약의 `label_ko`를 우선하고 기존 `label/name/id/code` fallback을 유지하도록 고정했다. **검증: `py -3.11 -X utf8 -B -m pytest -p no:cacheprovider app/test_evaluation_persistence.py app/test_evaluator_model_routing.py app/test_session_turn_persistence.py app/test_rbac_idor.py app/test_teacher_dashboard.py app/test_learner_dashboard.py -q` 59 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.**
|
||||
- 페르소나 생성 계약 11차 + 평가 저장 packet: `app/persona_generation_contract.py`가 draft structured schema, prompt bundle, legacy/structured `GenerateResponse` payload extraction, generated draft defaults/coercion을 소유한다. 기존 `routes/personas.py`는 teacher/admin gate, RAG evidence 수집, engine request, provenance 조립, DB/HTTP error mapping을 계속 담당한다. `SessionEvaluationWrite.from_result()/from_error()`는 session evaluation 저장 packet 생성을 소유해 route의 long keyword bag을 제거했다. Node conformance runner는 `StreamPacket.oneOf`, payload `$ref`, `gateway-default` request sentinel과 resolved response model 분리를 읽어 event/payload/default-routing 검증 중복을 줄였고, `GatewayPromptParts`는 current-turn prompt split 경계를 명명했다. **검증: persona contract+review 39 passed, backend focused 120 passed, Node conformance OK, gateway contract 27 passed, `npm run check:api-types`, `npm run typecheck`.**
|
||||
- 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 칩으로 파생 노출한다. 이번 보강에서는 `/health`·runtime readiness·deploy preflight가 음성 메타 5개 컬럼 전체를 확인하고, 실제 Session 마이크 UI가 `audio_end`에 browser voice activity/trailing silence 메타를 보내며, DB-backed voice WS/UI E2E가 `/review` `nonverbal` 칩까지 이어지는지 확인한다. raw transcript/text/provider/source/raw type은 응답에서 제외하고, 공개 공유 카드에는 축어록과 provider raw를 싣지 않는다. 실제 provider 기반 한숨·울음 감지, 역량 지표화 정책, live 마이크/WSS 장시간 실측은 후속. **검증: `python -X utf8 -m pytest -p no:cacheprovider app/test_runtime_policy.py app/test_session_turn_persistence.py app/test_voice_ws.py app/test_voice_service.py -q` 77 passed, `PLAYWRIGHT_PORT=5197 npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run --workers=1` 2 passed, `PLAYWRIGHT_PORT=5199 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` 5 passed, `npm run typecheck`, preflight skip-db PASS.**
|
||||
- M2 다회기 케이스 아크 11차: 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 인자 경계를 명시했다. 9차에서는 `DigestQualityAssessment`/`SessionDigestWorkerOutcome`로 LLM digest 후보 local quality harness를 추가해 빈/짧은 digest, raw forbidden substring, 내부 평가·CCD·상태 marker, 잘못된 `S{session_no}:` prefix를 fallback 유지 대상으로 판정한다. 10차에서는 `session_digest_worker.py`가 `CompressionJob`→`GenerateRequest` 변환, 주입형 engine/audit 호출, accepted-only `session_summary.digest/compressed_by/token_count`와 `case_profile.case_digest` 적용 계획, one-shot DB loader를 소유한다. 11차에서는 `scripts/run-session-digest-worker.py` dry-run/apply runner와 `SESSION_DIGEST_WORKER_ENABLED=false` 기본값의 세션 종료 background scheduler 골격을 추가하고, `compressed_by IS NULL` loader/apply CAS로 이미 압축된 세션 재실행 race를 막는다. LLM digest worker 입력 후보는 client-visible `text_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 승격 기준, 실 provider 장시간 운영, 임상 골든셋 품질평가, 재압축은 후속. **검증: `py -3.11 -X utf8 -B -m pytest -p no:cacheprovider app/test_session_digest_worker.py app/test_session_memory.py -q` 30 passed, `py -3.11 -X utf8 -B -m pytest -p no:cacheprovider app/test_session_digest_worker.py app/test_session_memory.py app/test_session_turn_persistence.py app/test_orchestrator_masking.py app/test_runtime_policy.py -q` 87 passed, `py -3.11 -X utf8 -B -m py_compile app/services/session_digest_worker.py app/services/memory.py app/test_session_digest_worker.py app/test_session_memory.py`, 기존 `py -3.11 -X utf8 -B -m pytest -p no:cacheprovider app/test_session_memory.py -q` 20 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 사례개념화 워크시트 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`로 보낸다. 2026-07-01에는 교수자 수동 session/turn 재평가가 persona 기본 `theory_target`보다 session `theory_mode`를 우선하고, turn 재평가 `TurnContext`에도 같은 값을 채우도록 보강했다. CBT 체인·이론부합 루브릭은 후속. **검증: `python -B -m pytest -p no:cacheprovider app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q` 31 passed, `C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest -p no:cacheprovider app/test_eval_routes.py -q` 5 passed, `C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest -p no:cacheprovider app/test_eval_routes.py app/test_notifications.py app/test_session_turn_persistence.py -q` 39 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.**
|
||||
- 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을 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 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.**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue