P4-P7 페르소나와 Tailnet 허용 보강
This commit is contained in:
parent
17b114f647
commit
7f4a2d90c7
12 changed files with 121 additions and 30 deletions
|
|
@ -68,7 +68,7 @@
|
|||
- 계약 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`, `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.**
|
||||
- 위기게이트 109 1차: 실제 자해·자살 신호는 LLM/엔진 호출 전 중단하고 `crisis_resource(109)`와 `conversation_stopped`를 REST/SSE/voice 응답에 싣는다. `app.safety_events` detail 적재와 교수자 대시보드 안전 알림 큐까지 연결했다. 안전 이벤트 DB insert payload도 회귀화했다. 임상 문안, 실시간 push/메일, 지역 자원 확장은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 20 passed, `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed.**
|
||||
- 교수자 성장 추적 1차: `teacher_dashboard`가 정규화된 턴 평가를 기반으로 학습자별 적절성·라포·기술 사용 추이를 집계하고, 교수 콘솔에 최근 회기/항목별 성장 카드와 안전 알림 큐를 함께 표시한다. **검증: `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed, `pytest app/` 119 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`.**
|
||||
- 페르소나 저작 CRUD 1차: teacher/admin 전용 `POST/GET/PUT /personas/drafts` 경로를 추가해 draft 생성·조회·편집·검수요청을 열고, 교수 콘솔에 JSON 초안 작성/불러오기/수정 UI를 연결했다. 승인된 카드만 학습자 catalog에 노출하는 기존 경계는 유지한다. P4~P7 실제 임상 콘텐츠 입력은 후속. **검증: `pytest app/test_persona_review.py -q` 19 passed, `pytest app/` 119 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`.**
|
||||
- 페르소나 저작 CRUD 2차: teacher/admin 전용 `POST/GET/PUT /personas/drafts` 경로를 추가해 draft 생성·조회·편집·검수요청을 열고, 교수 콘솔에 JSON 초안 작성/불러오기/수정 UI를 연결했다. 승인된 카드만 학습자 catalog에 노출하는 기존 경계는 유지한다. 추가로 `data/personas/P4.json`~`P7.json`을 저장소 관리 `PersonaCard`로 읽어 `materialize_seed_personas()`와 seed fallback catalog에 포함했다. 항목형 저작 UI, 루브릭·이론 콘텐츠 외부화, 임상팀 최종 검수/서면 evidence는 후속. **검증: `python -m pytest app/test_persona_review.py -q` 22 passed, 로컬 dev DB `materialize_seed_personas()` 결과 `approved_codes=P1,P2,P3,P4,P5,P6,P7`, 인증된 로컬 `GET /personas`도 `P1..P7` 반환.**
|
||||
- M1 비언어 이벤트 1차: voice learner turn에 이미 저장하던 `audio_ref`/`silence_ms`/`speech_rate`/`barge_in`을 리뷰 API `nonverbal` 이벤트로 노출하고, 회기 리뷰 축어록에 침묵·발화 속도·끼어듦·음성 입력 칩을 표시했다. 한숨·울음 감지와 live 마이크/STT/TTS 실측은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 17 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `session-review.spec.ts` 1 passed.**
|
||||
- M2 다회기 케이스 아크 1차: DB 세션 생성 시 `(persona_id, learner_id)` 기준 `case_profile`을 upsert하고, `session_no`를 트랜잭션 안에서 원자 증가시키며, `InProcSession.case_id`가 매회 새 `runtime_case_id`가 아니라 안정 `case_id`를 가리키게 수정했다. 시작/submit/stream/voice 경로는 직전 `session_summary` 기반 seed recall을 사용한다. `case_digest`/`pinned_fact` 실적재, episodic embedding writer, trajectory 갱신은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 18 passed, `pytest app/ -q` 119 passed, `npm run check:api-types`.**
|
||||
- C1 사례개념화 워크시트 1차: `SessionReviewResponse.caseWorksheet`와 리뷰 화면 read-only 카드가 탐색 11항목·호소 5영역·인지삼제/감정·보호/방해·생물심리사회 목표 초안을 축어록 근거 기반으로 제공한다. 편집·DB 저장, 임상 루브릭, AI 추출/채점, 교수자 검수는 후속. **검증: `pytest app/test_session_turn_persistence.py -q` 15 passed, `pytest app/ -q` 119 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `session-review.spec.ts` 1 passed.**
|
||||
|
|
@ -78,5 +78,5 @@
|
|||
- H4 LLM call audit 1차: 상담 생성(generate/stream)·fast-loop 평가·deep-loop 평가의 외부 LLM 호출 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency metadata만 적재한다. prompt/completion 본문은 저장하지 않고 감사 실패는 상담 루프를 막지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log`가 8→11로 3행 증가했다(session `2460d56c-a9cb-4a40-a175-9575d510a5e9`). 공개 Google OAuth 실제 `/turn` proof는 별도 B2 항목에 남긴다. **검증: `pytest app/test_orchestrator_masking.py app/test_evaluation_persistence.py app/test_session_turn_persistence.py -q` 27 passed, `pytest app/ -q` 119 passed.**
|
||||
- H4 동의 게이트 1차: `app_user.consent_at`를 런타임 테이블 readiness와 `SessionUser`/`Principal`에 포함하고, learner 전용 `POST /auth/consent`·`DELETE /auth/consent`를 추가했다. 동의가 없는 learner는 `POST /sessions`와 voice dev persona 시작이 `consent_required`로 막히고, 프론트 세션 시작 전 동의 체크 UI가 먼저 저장하도록 안내한다. E2E dev-login helper는 세션 테스트 전에 동의를 seed한다. 한국어 이름/기관 NER, guardian/legal 서명 동의서 evidence, 공개 Google OAuth 실제 `/turn` proof는 후속. **검증: `pytest app/ -q` 125 passed, `pytest app/test_auth_providers.py app/test_session_turn_persistence.py app/test_persona_review.py app/test_voice_ws.py -q` 64 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, local `/sessions` no-consent 403 + `/auth/consent` 200.**
|
||||
- RAG warm 동시성 안정화: layout/session E2E가 여러 세션을 빠르게 만들 때 BGE-M3 embedder가 동시에 여러 번 지연 로드되며 `tqdm` lock 예외와 API health/dev-login timeout이 반복됐다. `rag.py`에서 embedder load/encode를 process-wide `RLock`으로 직렬화하고, `sessions.py`의 `_warm_rag_caches`를 semaphore 1개로 제한해 warm task가 상담 요청 경로를 막지 않도록 했다. **검증: `pytest app/ -q` 125 passed, `npx playwright test e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts --project=chromium-single-run --project=chromium-desktop --workers=1` 11 passed.**
|
||||
- 공개/로컬/Tailnet 로그인 복구: public API 530 원인은 prod에서 개발 전용 `VIGNETTE_VOICE_POC_SAMPLE_TTS=true`가 fail-close된 것과 DB `app.admin_engine_config` 기본 행 부재였다. 2026-06-27 모바일 502는 cloudflared 로그의 `127.0.0.1:8001` origin refused와 일치했다. `scripts/start-public-runtime.ps1`는 public prod 기동 시 샘플 TTS를 강제로 끄고, 운영 DB에는 `claude_cli`/`127.0.0.1:9099` engine config 행을 복구했다. 추가로 dev/Tailnet에서는 public OAuth callback이 로컬/Tailnet 세션으로 붙지 않으므로 Google 버튼과 직접 시작 URL을 `local_oauth_unavailable`로 차단하고 dev-login만 사용한다. public OAuth state는 HttpOnly 쿠키에 묶인 HMAC 서명 토큰으로 복구해 API 재시작 뒤 콜백이 `invalid_state`로 떨어지지 않는다. callback 실패는 비밀값 없는 reason/status로 남기고, 로그인 화면도 token/state/provider/identity 실패 메시지와 reason code를 분리한다. 2026-06-27 18:25 KST에 stale Vite `allowedHosts`로 생긴 Tailnet 403을 `scripts/start-tailscale-runtime.ps1` 재기동으로 복구했다. **검증: `https://vignette.chanpaca.net/login` 200, `https://api-vignette.chanpaca.net/health` prod/db/engine true, public Google auth redirect 302 + HttpOnly state cookie, provider callback error → `/login?oauth=access_denied`, `https://alpaca-home.taile93291.ts.net/login` 200 + `/api/health` dev/db/engine true + `/api/auth/config` 200, local/Tailnet Google direct는 `local_oauth_unavailable`, local auth E2E 7 passed, Tailnet auth/dev-login E2E 2 passed.**
|
||||
- 공개/로컬/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 없음. 추가 확인 결과 기존 DB는 `POSTGRES_USER=vignette` 기반이고 API startup DDL이 owner 권한을 요구해 런타임 app-role 전환은 마이그레이션 owner/런타임 role 분리 후 진행해야 한다.**
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
| 1 | `services/orchestrator.py:41,185,221-225,259,324-326,401` | `LogHook` 타입 + `log_hook` 파라미터 + 두 if 분기 + `__all__` export가 완전 데드코드. caller 0건(`grep 'log_hook='` → 0), 두 호출부(`sessions.py:916`, `voice.py:307`)가 모두 생략 → 항상 None 도달불가 | medium | 타입·파라미터·두 분기·export 제거. 턴 로깅이 필요하면 라우트에서 배선, 아니면 확장점 전체 삭제 |
|
||||
| 2 | `routes/voice.py:307` | 음성 라우트가 `eval_hook` 미주입 → 음성 턴 항상 `evaluation=None`. 텍스트(`sessions.py:916`)는 주입하는 비대칭. (orchestrator 분기 자체는 텍스트 경로로 도달하므로 데드 아님) | medium | 음성 턴도 평가 대상이면 `eval_hook=evaluator.make_eval_hook(engine_client)` 추가. 의도적 제외면 사유 주석 명시 |
|
||||
| 3 | 서버 `services/voice.py:223,132`·`routes/voice.py:378` / 프론트 `Session.tsx:913-1001,58-68` | 서버 립싱크 RMS 힌트(`tts_chunk.rms/seq`)가 프론트에서 완전 dead. `onmessage`가 해당 type 미처리, `VoiceEvent` 인터페이스에 필드 선언조차 없음. 실제 립싱크는 클라이언트 `AnalyserNode`가 자체 계산 → 두 RMS 산출 공존, 서버 쪽 폐기 | medium | 서버 RMS 미사용이면 `estimate_chunk_rms`·`TTSChunk.rms/seq`·`tts_chunk` 메타 제거(서버 CPU·프로토콜 cruft 제거). 또는 클라이언트 경로 제거 후 서버 힌트로 일원화 |
|
||||
| 4 | `data/personas/P4~P7.json` (+`services/persona.py:383`) | 백엔드가 P4~P7 JSON을 전혀 로드 안 함. 런타임 시드는 `SEED_PERSONAS={P1,P2,P3}` 하드코딩이 전부, `data/personas` 읽는 로더 0건. 완성된 카드·아바타 자산이 있어도 학습자가 내담자로 인스턴스화 불가 | medium | 사용 의도면 로더/마이그레이션 추가 또는 `SEED_PERSONAS` 편입, 미사용이면 삭제. 프론트 `live2dModel.ts`의 P4~P7 정의와 정합 필요 |
|
||||
| 4 | `data/personas/P4~P7.json` (+`persona_repository.py`) | 해결됨. `load_file_personas()`/`built_in_personas()`가 저장소 P4~P7 JSON을 `PersonaCard`로 읽고, `materialize_seed_personas()`와 seed fallback catalog에 포함한다. | done | 후속은 항목형 저작 UI, 임상팀 최종 검수 evidence, 프론트 아바타 정의와의 표현 정합. |
|
||||
| 5 | `public/live2d/personas/p4~p7/**` (+`dist` 미러) | Live2D 모델 자산이 런타임 fetch 안 됨. 실제 아바타는 SVG 컴포넌트로 렌더, `live2dModel3Path()`가 만든 URL은 `data-live2d-model-url` 속성 문자열로만 노출(fetch/cubism/pixi grep → 0). e2e도 속성 문자열만 어서션 | medium | Live2D 제거 기조(최근 커밋)와 맞춰 디스크 자산 트리 삭제 또는 빌드 제외. 남길 경우 실제 로더 부착 |
|
||||
|
||||
### 3.2 과다 파라미터 함수 → 파라미터 객체화
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
|
||||
- **`ruff --fix`(F401/F811/F841)** 미사용 import·재정의·지역변수 자동 정리. 리뷰 부담 거의 0.
|
||||
- **명백한 데드코드 삭제**: `LogHook` 타입·`log_hook` 파라미터·두 분기·`__all__` export(caller 0건 입증됨).
|
||||
- **고아 자산 제거**: Live2D 모델 트리(`public/live2d/personas/**` + dist 미러, fetch 주체 0건), 사용 계획 없는 `data/personas/P4~P7.json` — 단, 프론트 `live2dModel.ts` 정의와의 정합 확인 후.
|
||||
- **고아 자산 제거**: Live2D 모델 트리(`public/live2d/personas/**` + dist 미러, fetch 주체 0건). `data/personas/P4~P7.json`은 seed/catalog 로더에 연결됐으므로 삭제 대상이 아니다.
|
||||
- **Type-1 동일 함수 통합**: `_iso`를 `app/timefmt.py`로 단일 정의.
|
||||
- **음성 `eval_hook` 누락 보충**(`voice.py:307`): 텍스트 경로와 동일한 한 줄 주입(단, 음성 턴 평가가 제품 의도인지 먼저 확인 — 의도적 제외라면 주석만).
|
||||
- **DTO 드리프트 즉시 보충**: `crisis_kind`/`end_state` 등 누락 필드를 TS에 수동 추가(자동생성 전 최소 조치).
|
||||
|
|
|
|||
|
|
@ -69,10 +69,10 @@
|
|||
- **현재상태**(✓직접검증): `make_eval_hook`이 submit/voice 생성 경로에 주입되고, stream은 `_evaluate_stream_turn`으로 fast-loop 평가를 붙인다. 결과는 `feedback_scores`, `alternative_utterance` 등 정규화 테이블에 적재·hydrate된다.
|
||||
- **권고**: 회기리뷰 UI 좌(축어록 타임라인+비언어)/우(기법·적절성·대안반응·이론) 2열 고도화, 원천 축어록 few-shot 골든셋 적재.
|
||||
|
||||
### [high] H3. 임상팀 콘텐츠 입력 경로(페르소나 저작 CRUD) 부재 + P4~P7 미적재
|
||||
### [high] H3. 임상팀 콘텐츠 입력 경로(페르소나 저작 CRUD) 2차 구현·임상 검수 잔여
|
||||
- **근거**: doc3(R&R), doc4(페르소나=전문가 산출물)
|
||||
- **현재상태**(✓직접검증): personas 라우트에 검수 승인/반려만, draft 생성·편집 API 없음. persona_repository는 in-code SEED(P1~P3)만, 외부 JSON 미로드.
|
||||
- **권고**: 페르소나 저작 CRUD(draft→review)와 P4~P7 적재, 루브릭·이론 콘텐츠를 임상팀 편집 가능 데이터로 외부화.
|
||||
- **현재상태**(재검증): teacher/admin draft 생성·조회·편집·검수요청 API와 교수 콘솔 JSON 초안 패널을 연결했다. persona_repository는 in-code SEED(P1~P3)와 `data/personas/P4.json`~`P7.json`을 합쳐 `materialize_seed_personas()`와 seed fallback catalog에 포함한다.
|
||||
- **권고**: JSON 대신 항목형 저작 UI, 루브릭·이론 콘텐츠 외부화, P4~P7 포함 임상팀 최종 검수/서면 evidence 확보.
|
||||
|
||||
### [high] H4. PII 마스킹 한국어 이름/기관 NER + 동의 게이트 잔여
|
||||
- **근거**: doc1/2/5(실명·날짜·장소·금액·미성년·자살시도 다수), doc4(IRB·개인정보)
|
||||
|
|
@ -114,7 +114,7 @@
|
|||
- C2 1차 완료: escalate 시 `safety_events` insert, prepare_turn의 `ideation_observed` 전달, 109 resource, 교수자 알림 큐까지 배선했다. 후속은 임상 스크립트·서약 문안·감점 루브릭.
|
||||
- C3 1차 완료: `theory_mode`가 세션·평가·생성 프롬프트까지 흐른다. 후속은 임상팀 CBT 체인·이론부합 루브릭·명시적 선택 UI.
|
||||
- H2 1차 완료: `make_eval_hook`과 stream 평가가 턴 파이프라인에 붙고 정규화 테이블로 적재·복원된다. 대안발화도 `app.alternative_utterance`로 정규화한다. 후속은 2열 리뷰 UI·골든셋.
|
||||
- H3: 페르소나 저작 CRUD + P4~P7 로드 경로.
|
||||
- H3: 페르소나 항목형 저작 UI + 루브릭·이론 콘텐츠 외부화 + 임상팀 최종 검수 evidence.
|
||||
- H4(부분): 한국어 이름/기관 NER, guardian/legal 서명 동의 evidence. LLM call audit 적재, 로컬 live row proof, 학습자 동의 수락/철회/회기 시작 하드게이트 골격은 완료.
|
||||
- X1: dry-run JSONL export·PII scan·IAA 계산 1차 완료. `ds.*` write는 `--write-dataset` 명시 시에만 수행한다. X2 비용 관측·budget alert와 evaluator 모델 override 1차는 완료했고 semantic cache·장기 한도 정책은 후속. M1/M2도 1차 구현 완료, provider 기반 비언어 감지와 case_digest/pinned_fact 실적재는 후속.
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
- web `127.0.0.1:5173`
|
||||
- 2026-06-27 17:00 KST 재확인: Tailnet `GET /login`이 `403 Blocked request. This host ... is not allowed`를 반환하던 원인은 Vite가 stale env로 떠서 `VITE_ALLOWED_HOSTS=alpaca-home.taile93291.ts.net`를 반영하지 못한 상태였다. `scripts/start-tailscale-runtime.ps1 -ApiPort 8010 -WebPort 5173 -SkipServeUpdate` 재기동 후 `GET /login` 200, `GET /api/health` dev/db/engine true, direct Google start 302 `local_oauth_unavailable`로 복구 확인.
|
||||
- 2026-06-27 18:25 KST 재확인: 같은 stale Vite `allowedHosts` 증상이 재발해 `scripts/start-tailscale-runtime.ps1 -ApiPort 8010 -WebPort 5173`로 스택을 재기동했다. 현재 `https://alpaca-home.taile93291.ts.net/login` 200, `/api/health` 200(`dev`, `db:true`, `engine:true`), `/api/auth/config` 200이다. 함께 확인한 공개 경로도 `https://vignette.chanpaca.net/login` 200, `https://api-vignette.chanpaca.net/health` 200(`prod`, `db:true`, `engine:true`)이다.
|
||||
- 2026-06-27 18:57 KST 재확인: `https://alpaca-home.taile93291.ts.net/login` 200, env 없는 임시 Vite host-header smoke(`Host: alpaca-home.taile93291.ts.net`) 200, `/api/health` 200(`dev`, `db:true`, `engine:true`), `/api/auth/config` 200(`dev_login_enabled:true`). `alpaca-home.taile93291.ts.net`를 Vite 기본 `allowedHosts`에도 포함해 스크립트 밖 수동 Vite 기동 시 403 재발 가능성을 낮췄다.
|
||||
|
||||
## 공개 vnet.18ka.net 상태
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue