현재 작업 전체 반영

This commit is contained in:
Yun Chan 2026-06-27 16:08:41 +09:00
parent 5560638e54
commit c0dddab594
85 changed files with 11322 additions and 539 deletions

View file

@ -6,7 +6,7 @@
분류: **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 사례개념화 산출물·C2 위기개입 프로토콜·C3 이론모드/CBT가 신규 critical. 이는 아래 B1~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 사례개념화 산출물, C2 위기개입 프로토콜, C3 이론모드 스레딩은 1차 구조를 만들었다. 콘텐츠 정의는 임상팀(구훈정·어유경) 소유라 코드는 구조를 선제 구축하되 임상 문안과 평가기준은 외부 정의로 받는다.
---
@ -22,13 +22,13 @@
## B2. 환경 제약 — 이 워크스테이션에서 증거 생산 불가
- [ ] **`vnet.18ka.net` 공개 live 전환** — 코드/스크립트는 `vnet.18ka.net`·`api-vnet.18ka.net`을 허용하도록 갱신 완료. Tailnet 주소 `https://alpaca-home.taile93291.ts.net`로그인까지 검증됨. 공개 vnet은 현재 (1) `vnet.18ka.net`/`api-vnet.18ka.net` DNS 부재, (2) cloudflared 현재 인증 컨텍스트가 `18ka.net` zone이 아니라 `chanpaca.net` zone에 route dns를 생성, (3) prod API용 Postgres `127.0.0.1:55432` 미기동으로 8001 fail-closed, (4) Google Console redirect URI `https://api-vnet.18ka.net/auth/callback` 등록 필요 때문에 live 완료 아님. 상세: `docs/ops/tailscale-vnet-runtime-2026-06-27.md`.
- [ ] **`vnet.18ka.net` 공개 live 전환** — 코드/스크립트는 `vnet.18ka.net`·`api-vnet.18ka.net`을 허용하도록 갱신 완료. Tailnet 주소 `https://alpaca-home.taile93291.ts.net`dev-login까지 검증됐고, public OAuth redirect가 로컬/Tailnet 세션으로 붙지 않는 혼선을 막기 위해 Tailnet Google 버튼과 직접 Google 시작 URL은 `local_oauth_unavailable`로 차단한다. 공개 vnet은 현재 (1) `vnet.18ka.net`/`api-vnet.18ka.net` DNS 부재, (2) cloudflared 현재 인증 컨텍스트가 `18ka.net` zone이 아니라 `chanpaca.net` zone에 route dns를 생성, (3) Google Console redirect URI `https://api-vnet.18ka.net/auth/callback` 등록 필요 때문에 live 완료 아님. 현재 prod API 8001 자체는 `db:true`, `engine:true`로 건강하다. 상세: `docs/ops/tailscale-vnet-runtime-2026-06-27.md`.
- [ ] **공개 Google OAuth 실제 `/turn` proof** — 로그인 가능한 계정으로 `storageState` 캡처 후 `E2E_PUBLIC_AUTH=1` + `chromium-public-auth` 1회 통과 필요. (소유자 지시로 이번엔 보류.)
- [ ] **음성 캐스케이드 live** — 실제 Deepgram STT / OpenAI TTS provider 키, 물리 마이크, 공개 WSS 50분 양방향 실측. provider 키/하드웨어 부재로 미실행(백엔드 계약·UI synthetic 경로는 단위/E2E로 증명됨).
- [ ] **claude_cli ↔ Messages API 폴백 동일성**`ANTHROPIC_API_KEY`가 있어야 Messages API 경로를 돌려 동일성 검증 가능. claude_cli 경로는 게이트웨이 probe로 live 실측 완료.
- [ ] **저항엔진 openness 곡선 DB 실증** — fast-loop 평가의 `client_state_read``app.turn_client_state` 적재 경로는 추가됨. 다만 dev DB 실데이터/다수 실턴(real LLM) 기반 openness 곡선 증거는 아직 필요. 현재는 `app.test_state_machine_resistance` 단위테스트와 `app.test_evaluation_persistence` 매핑 테스트로 1차 증명.
- [ ] **마스킹 게이트 운영 외부-send 관측** — `audit.llm_call_log` 0행(앱이 payload를 적재하지 않음). 엔진으로 나가는 실제 요청 payload를 캡처해 raw PII 부재를 관측하려면 로깅 경로 또는 프록시 캡처 필요. 현재는 `app.test_orchestrator_masking`로 generate/stream payload에 phone/email/RRN 부재 증명.
- [ ] **재부팅 후 watchdog smoke**`watch-public-runtime.ps1` + Scheduled Task가 재부팅 후 엔진/API/터널을 복구하고 public `/turn`이 통과하는지 실측. 재부팅 불가로 미실행(parser/ check-only 경로는 확인됨).
- [ ] **마스킹 게이트 운영 외부-send live proof** — 상담 생성(generate/stream)과 fast/deep 평가 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency만 적재하는 경로를 추가했다. prompt/completion 본문은 저장하지 않는다. 남은 증거는 실제 운영 로그인 `/turn` 후 live DB row를 확인하는 것이다. 현재는 `app.test_orchestrator_masking`로 generate/stream payload에 phone/email/RRN 부재와 감사 payload의 본문 미포함을 증명.
- [ ] **재부팅 후 watchdog smoke**`watch-public-runtime.ps1` + Scheduled Task가 재부팅 후 엔진/API/터널을 복구하고 public `/turn`이 통과하는지 실측. 재부팅 불가로 미실행(parser/ check-only 경로는 확인됨). 2026-06-27 점검에서 아직 DNS가 없는 `api-vnet.18ka.net`은 기본 watchdog 검사에서 제외했고, 개통 후 `-AdditionalPublicHealthUrls`로 명시 추가하도록 runbook을 보강했다.
- [ ] **상주 엔진풀 RSS 실측** — probe로 TTFT/세션재사용/cost는 live 확인. 프로세스 RSS(메모리)는 별도 OS 계측 항목으로 남김.
---
@ -54,7 +54,7 @@
## Phase 3 파일럿 게이트 (실참여자 필요)
- [ ] 20명 교육용 파일럿 운영 / 효과성·KPI 측정(SUS·자기효능감·κ/ICC·환각률) / 재귀학습 데이터셋 1차 산출 / 개인정보·동의 감사. 문서/checker는 준비됨(`docs/phase3/*`, `scripts/check-phase3-artifacts.py`), 실제 evidence는 파일럿 시점.
- [ ] 20명 교육용 파일럿 운영 / 효과성·KPI 측정(SUS·자기효능감·κ/ICC·환각률) / 재귀학습 데이터셋 approved 산출 / 개인정보·동의 감사. 문서/checker와 dry-run exporter는 준비됨(`docs/phase3/*`, `scripts/check-phase3-artifacts.py`, `scripts/export-recursive-dataset.py`), 실제 approved evidence는 파일럿·steward/legal 검수·IAA 통과 시점.
---
@ -64,4 +64,17 @@
- live 운영 증거: engine config 운영값, 상주 엔진풀 probe(TTFT/cost/세션재사용), Postgres RLS/audit smoke 5 checks PASS, turn cost telemetry(app.turns 13행) — DONE.
- 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 역할 프로비저닝.
- 평가 SSOP 1차: `TurnRecord.evaluation``app.feedback_scores`/`turn_technique`/`turn_client_state`/`supervisor_comment`로 정규화 적재·복원. 원시 평가 테이블은 evaluator/admin RLS로 제한하고 서버가 evaluator 컨텍스트로 적재한다. `app.turns` insert는 `RETURNING id` 확인으로 silent success 제거. deep-loop `alternative_utterance` 정규화는 후속. **검증: `app.test_session_turn_persistence` + `app.test_evaluation_persistence` 14 tests OK.**
- 평가 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.**
- 계약 SSOT 1차: FastAPI OpenAPI를 `apps/web/src/lib/api.gen.ts`로 생성하는 `npm run generate:api-types`와 stale 체크용 `npm run check:api-types`를 추가했다. `TurnResponse`/`SessionEndResponse`는 생성 타입 alias를 사용해 기존 `crisis_kind`/`end_state` 드리프트를 닫았다. 나머지 수기 미러 DTO 전환과 CI 연결은 후속. **검증: `npm run check:api-types`, `npm run typecheck`, `pytest app/` 119 passed, `pytest engine_gateway/` 7 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`.**
- 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.**
- C3 이론모드 1차: `theory_mode``TurnContext`/sessions/voice/evaluator뿐 아니라 `build_turn_messages`의 엔진 메시지까지 전달된다. 프론트는 `persona.theory_target` 기준으로 시작해 `humanistic` 하드코딩을 제거했다. CBT 체인·이론부합 루브릭·명시적 선택 UI는 후속. **검증: `pytest app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q` 20 passed, `pytest app/ -q` 119 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` 22 passed, `pytest app/ -q` 119 passed.**
- X2 예산 경고 1차: `ADMIN_USAGE_BUDGET_USD` 설정값을 기준으로 `GET /admin/usage``budget.status=disabled|ok|warn|exceeded`, 사용률, 잔여 예산을 반환하고 관리자 콘솔이 예산 상태 배너를 표시한다. 80% 이상 warn, 100% 이상 exceeded. 캐싱·저비용 모델 분기는 후속. **검증: `pytest app/test_runtime_policy.py -q` 20 passed, `pytest app/ -q` 119 passed, `npm run check:api-types`, `npm run typecheck`.**
- 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 본문은 저장하지 않고 감사 실패는 상담 루프를 막지 않는다. 실제 운영 `/turn` row 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.**
- 공개/로컬/Tailnet 로그인 복구: public API 530 원인은 prod에서 개발 전용 `VIGNETTE_VOICE_POC_SAMPLE_TTS=true`가 fail-close된 것과 DB `app.admin_engine_config` 기본 행 부재였다. `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를 분리한다. **검증: `https://vignette.chanpaca.net/login` 200 + `index-BzQmv-jU.js`, `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`, local/Tailnet Google direct는 각 origin의 `/login?oauth=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 분리 후 진행해야 한다.**

View file

@ -36,6 +36,16 @@ The installer is idempotent. Re-running it updates the same task:
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-public-runtime-task.ps1 -IntervalMinutes 5
```
Do not add future domains to the default watchdog until DNS and Cloudflare
routing are live. For example, `api-vnet.18ka.net` is intentionally excluded
from the default checks while that domain has no DNS. After a public domain is
actually reachable, add it explicitly:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-public-runtime-task.ps1 `
-AdditionalPublicHealthUrls https://api-vnet.18ka.net/health
```
Task name:
```powershell
@ -62,6 +72,14 @@ Invoke-RestMethod http://127.0.0.1:8001/health
Invoke-RestMethod https://api-vignette.chanpaca.net/health
```
Optional extra public host check, only after the host resolves:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\watch-public-runtime.ps1 `
-CheckOnly `
-AdditionalPublicHealthUrls https://api-vnet.18ka.net/health
```
Optional real Claude CLI readiness smoke:
```powershell

View file

@ -16,13 +16,13 @@
| 주장 | 직접 검증 결과 |
|---|---|
| C1 사례개념화/인지삼제/4사분면 구조 부재 | `cognitive_triad/case_conceptual/인지삼제/4사분면/quadrant` grep **0건** ✓ |
| C2 위기 escalate가 safety_events 미적재 | `orchestrator.py:281` escalate 시 `StreamEvent("safety")`만 yield, DB insert·교수자 알림 **없음** ✓ (stream 신호는 존재) |
| C3 이론모드 미주입 | `services/persona.py:147 build_turn_messages` 시그니처에 `theory` 인자 **없음** + `Session.tsx:589` `"humanistic"` 하드코딩 ✓ |
| H2 턴별 평가 미가동 | `make_eval_hook` 정의·export(`evaluator.py:738`)됐으나 `routes/`에서 주입 **0건** ✓ |
| C1 사례개념화/인지삼제/4사분면 구조 | `SessionReviewResponse.caseWorksheet`와 리뷰 화면 read-only 카드 1차 구현. 편집·저장·임상 루브릭·AI 채점은 후속 ✓ |
| C2 위기 escalate 배선 | 실제 위기 신호는 엔진 호출 전 중단하고 109 리소스·`conversation_stopped`를 반환한다. `crisis.risk_level``ideation_observed`로 전달되고, escalate 시 `app.safety_events` detail 적재 및 교수자 대시보드 알림 큐로 연결된다. 안전 이벤트 DB insert payload는 회귀 테스트로 고정했다. |
| C3 이론모드 1차 배선 | `theory_mode`가 세션·평가·생성 프롬프트까지 전달되고 `Session.tsx` 하드코딩 제거 ✓ |
| H2 턴별 평가 1차 가동 | `make_eval_hook`이 submit/voice에 주입되고 stream 평가도 붙어 정규화 적재·복원 ✓ |
| H3 페르소나 저작 경로 부재 | `persona_repository.py`는 in-code `SEED_PERSONAS`(P1~P3)만 materialize, draft CRUD·외부 JSON 로드 **없음** ✓ |
그 외 항목(H1 KPI 수집, H4 한국어 PII, M1~M3, X1~X2, L1)은 분석 결과이며 착수 전 코드 재확인 권고. doc4 KPI 범위(κ/ICC·환각률)는 신청서 본문 미명시 — 별도 평가설계 문서 근거 가능성(과대해석 금지).
그 외 항목(H1 KPI 수집, H4 한국어 이름/기관 NER와 live proof, M1~M2, X1~X2, L1)은 분석 결과이며 착수 전 코드 재확인 권고. M3은 1차 구현 후 재검증 완료. doc4 KPI 범위(κ/ICC·환각률)는 신청서 본문 미명시 — 별도 평가설계 문서 근거 가능성(과대해석 금지).
---
@ -30,8 +30,8 @@
원천문서가 요구하는 다수 차원 대비 Vignette가 **가장 부족한 핵심 3가지**:
1. **임상 산출물 구조의 전면 부재** — doc1/doc4/doc5가 1급 산출물로 요구하는 사례개념화 워크시트(11탐색항목·호소 5영역·인지삼제·1·2차 감정·보호/방해 4사분면·생물심리사회 목표)가 코드·스키마에 전무.
2. **위기개입 안전 프로토콜의 미구현** — IRB 100% 준수 전제인 비밀보장 예외 고지·자살사고 단계적 탐색·생명유지서약·에스컬레이션이 없고, escalate=True여도 safety_events 적재·교수자 알림이 없음.
1. **임상 산출물 구조의 1차 구현과 고도화 필요** — doc1/doc4/doc5가 1급 산출물로 요구하는 사례개념화 워크시트(11탐색항목·호소 5영역·인지삼제·1·2차 감정·보호/방해 4사분면·생물심리사회 목표)는 리뷰 API와 read-only UI 초안으로 1차 구현됐다. 편집·DB 저장, 임상 루브릭, AI 추출/채점, 교수자 검수는 아직 후속이다.
2. **위기개입 안전 프로토콜의 1차 배선 완료와 임상 고도화 필요** — 실제 위기 신호는 LLM 호출 전 중단하고 109 안내·`safety_events`·교수자 대시보드 알림 큐로 연결한다. 남은 것은 비밀보장 예외 고지, 자살사고 단계적 탐색, 생명유지서약, 위기탐색 누락 감점 같은 임상 콘텐츠·루브릭이다.
3. **계약 평가 KPI 수집 체계의 부재** — doc4가 못박은 자기효능감·기술숙련도·수련만족도 3종 사전사후 비교와 20명 실험/통제군 설계가 코드에 0건.
## 2. 원천문서 5종 요지
@ -44,40 +44,40 @@
## 3. 부족한 부분 — 심각도 우선순위
### [critical] C1. 사례개념화·치료계획 산출물 구조 전면 부재
### [critical] C1. 사례개념화·치료계획 산출물 구조 1차 구현·고도화 필요
- **근거**: doc1, doc4(mockup '상담 설정·피드백 리포트'), doc5
- **현재상태**(✓직접검증): 사례개념화/cognitive_triad/4사분면 grep 0건. CCD는 숨겨진 정답키일 뿐 학습자 산출물 폼 아님.
- **권고**: 사례개념화 워크시트 스키마(11항목/5영역/인지삼제/4사분면/생물심리사회 목표)를 입력 폼으로, AI가 축어록에서 신념·감정·욕구 초안 추출·제안, 규칙 기반 채점. 루브릭은 임상팀이 외부 설정으로 정의 가능하게 외부화.
- **현재상태**(✓직접검증): `SessionReviewResponse.caseWorksheet`와 리뷰 화면 read-only 카드가 축어록 근거 기반 초안을 제공한다. CCD는 숨겨진 정답키이고, 현재 워크시트는 저장형 학습자 제출물은 아님.
- **권고**: 편집·DB 저장형 사례개념화 워크시트, AI 축어록 초안 추출 고도화, 규칙 기반 채점, 교수자 검수 플로우를 추가한다. 루브릭은 임상팀이 외부 설정으로 정의 가능하게 외부화.
### [critical] C2. 위기개입 프로토콜·생명유지서약·에스컬레이션 미구현
### [critical] C2. 위기개입 프로토콜·생명유지서약·에스컬레이션 1차 배선 완료·임상 고도화 필요
- **근거**: doc1, doc5(핵심 훈련 시나리오), doc4(IRB 전제)
- **현재상태**(✓직접검증): escalate=True 시 client stream 이벤트만, safety_events insert·교수자 알림 코드 없음. prepare_turn이 risk_level을 state_machine.evolve의 ideation_observed로 미전달.
- **권고**: (1) 위기 분기 상태(예외고지→단계적 탐색→서약 유도)를 상태머신에 추가, (2) escalate 시 safety_events insert + 교수자 알림, (3) 학습자 위기탐색 누락 시 회기리뷰 감점, (4) ideation_observed 전달.
- **현재상태**(✓직접검증): 실제 자해·자살 신호는 REST/SSE/voice에서 엔진 호출 전 중단하고 `crisis_resource(109)`, `conversation_stopped`를 반환한다. `prepare_turn`은 crisis risk level을 `ideation_observed`로 전달하고, `record_safety_event``app.safety_events`에 109 resource·turn_seq·matched detail을 적재한다. 교수자 대시보드는 `list_safety_alerts` 기반 안전 알림 큐를 노출한다.
- **권고**: 후속은 콘텐츠/정책 영역이다. 위기 분기 스크립트(비밀보장 예외고지→단계적 탐색→서약 유도), 실시간 push/메일 알림 정책, 학습자 위기탐색 누락 시 회기리뷰 감점 루브릭을 임상팀 산출물로 외부화한다.
### [critical] C3. 이론모드 미주입 + CBT 콘텐츠 자체 부재
### [critical] C3. 이론모드 1차 배선 완료 + CBT 콘텐츠/선택 UI 잔여
- **근거**: doc4(humanistic+CBT 필수), doc2/doc5
- **현재상태**(✓직접검증): build_turn_messages에 theory 인자 없음, Session.tsx:589 'humanistic' 하드코딩. CBT 프롬프트 체인·이론부합 루브릭 전무.
- **권고**: theory 인자 추가 + 이론별 단계적 프롬프트 체인(공감·반영 / 인지재구조화·행동활성화) + 프론트 이론 선택 UI + 이론 부합도 스코어링 루브릭.
- **현재상태**(✓직접검증): `TurnContext`/`prepare_turn`/sessions/voice/evaluator에 `theory_mode`가 전달되고, `build_turn_messages`도 인간중심·CBT·통합 이론 프레이밍을 엔진 메시지에 넣는다. 프론트는 `persona.theory_target` 기준으로 시작해 기존 `humanistic` 하드코딩을 제거했다.
- **권고**: 임상팀 확정 CBT 단계 프롬프트 체인(공감·반영 / 인지재구조화·행동활성화), 이론부합 스코어링 루브릭, 명시적 프론트 이론 선택 UI. 현재는 페르소나 설계 이론 자동 선택이다.
### [high] H1. 계약 평가 KPI(자기효능감·기술숙련도·수련만족도 사전사후) 수집·집계 전무
- **근거**: doc4(20명 실험/통제군·단회기 50분·3척도 pre-post)
- **현재상태**: 자기효능감/사전사후/수련만족/실험통제군 grep 0건. Phase3 KPI도 report shape만, 계산 코드 0줄.
- **권고**: 3척도 pre-post 폼, 실험/통제군 배정, 자동누적 대시보드, 추이 시각화, 검정 계산 코드. (κ/ICC·환각률은 doc4 미명시 — 평가설계 문서 확정 필요.)
### [high] H2. 턴별 회기 리뷰(상호작용 분석 2열) 미가동
### [high] H2. 턴별 회기 리뷰 fast-loop 1차 가동 + 골든셋/2열 UI 잔여
- **근거**: doc2, doc5(골드 포맷: 기법 태깅+적절성+대안반응+이론해석)
- **현재상태**(✓직접검증): make_eval_hook 정의·export됐으나 라우트 미주입 → 턴별 fast-loop 미동작(회기종료 deep-loop만). few-shot 골든셋 기본 OFF.
- **권고**: eval_hook을 turn 파이프라인에 주입. 회기리뷰 UI 좌(축어록 타임라인+비언어)/우(기법·적절성·대안반응·이론) 2열. 원천 축어록을 채점 few-shot 골든셋으로 적재.
- **현재상태**(✓직접검증): `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 미적재
- **근거**: doc3(R&R), doc4(페르소나=전문가 산출물)
- **현재상태**(✓직접검증): personas 라우트에 검수 승인/반려만, draft 생성·편집 API 없음. persona_repository는 in-code SEED(P1~P3)만, 외부 JSON 미로드.
- **권고**: 페르소나 저작 CRUD(draft→review)와 P4~P7 적재, 루브릭·이론 콘텐츠를 임상팀 편집 가능 데이터로 외부화.
### [high] H4. PII 마스킹 한국어 공백 + 외부전송 관측 부재
### [high] H4. PII 마스킹 한국어 공백 + 외부전송 live proof 잔여
- **근거**: doc1/2/5(실명·날짜·장소·금액·미성년·자살시도 다수), doc4(IRB·개인정보)
- **현재상태**(분석): Presidio language='en' 고정으로 한국어 이름/주소/기관 미탐지, 폴백은 번호·이메일만. audit.llm_call_log 0행(런타임 미관측). consent_at 컬럼만, 동의 수집/게이트/철회 엔드포인트 전무.
- **권고**: 한국어 PII 탐지 추가, 마스킹 미들웨어 하드게이트화, llm_call_log 적재로 런타임 관측, 미성년/guardian 동의 수집·하드게이트·철회.
- **현재상태**(재검증): Presidio language='en' 고정으로 이름/기관명은 NER 보강이 필요하다. 한국어 날짜·금액·행정구역 주소 정규식 폴백은 추가됐고, 상담 생성(generate/stream)·fast/deep 평가 호출 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency만 적재한다. prompt/completion 본문은 저장하지 않는다. consent_at 컬럼만, 동의 수집/게이트/철회 엔드포인트 전무.
- **권고**: 한국어 이름/기관 NER 추가, 실제 운영 로그인 `/turn``audit.llm_call_log` row 실측, 미성년/guardian 동의 수집·하드게이트·철회.
### [medium] M1. 비언어/준언어 임상 이벤트 캡처·태깅 부재
- **근거**: doc1/2/5(침묵 초·한숨·울음·떨림을 1급 단서)
@ -86,22 +86,22 @@
### [medium] M2. 다회기 종단 케이스 아크·교차회기 사례개념화 미구동
- **근거**: doc1(2~10회기), doc2(3회기 자기개념 진화)
- **현재상태**(분석): case_state.ccd_estimate/presenting_arc/alliance_level 스키마만, build_recall_context() 빈 컨텍스트 반환. 음성 경로 빈 RecallContext.
- **권고**: case_state 런타임 구동, build_recall_context 실제 회상/pinned_fact 주입, 접수면접→다회기 연속성·자기개념 진화.
- **현재상태**: 1차로 `(persona_id, learner_id)` 안정 `case_profile` upsert, 원자적 `session_no`, 직전 `session_summary` 기반 seed recall, REST/SSE/voice recall cache 주입을 연결했다. `case_digest`/`pinned_fact` 실적재, episodic embedding writer, trajectory 갱신은 아직 없다.
- **권고**: case_state 런타임 보강, build_recall_context의 pinned_fact·case_digest 실적재, 접수면접→다회기 연속성·자기개념 진화 실증.
### [medium] M3. SSO claim 매핑·식별자 안정성·deprovisioning 감사 미연결
### [medium] M3. SSO claim 매핑·식별자 안정성 1차 완료·운영 IdP 감사 미연결
- **근거**: doc3(교내 SSO), doc4(RBAC·익명화)
- **현재상태**(분석): Google/SAML 콜백 cohort_ids=[] 하드코딩, role이 email allowlist, external_id가 'email:{}' 파생(불안정). SAML 서명검증 미구현. role변경/삭제 audit 미기록. 거버넌스 증거(claim-map·동의 매트릭스) 부재.
- **권고**: SSO claim→role/cohort/institution_user_id 매핑+안정 식별자(sub/NameID), SAML 서명검증, deprovisioning audit, 한신 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.
### [medium/추적] X1. 재귀학습·데이터셋 export 파이프라인 구현
- **현재상태**(분석): ds.* 스키마(κ/ICC 컬럼)만, read/write 코드 0건. JSONL export·IAA 게이트·골든셋 승격 미코딩.
- **권고**: JSONL export 잡, IAA 게이트(κ≥0.6/ICC≥0.75), 골든셋 승격.
### [medium/추적] 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` 승격 검증.
### [medium/추적] X2. AI API 비용 관측 부재
### [medium/추적] X2. AI API 비용 관측·예산 경고 1차 완료·최적화 미구현
- **근거**: doc3(회의록이 'AI API 비용'을 운영 리스크로 명시)
- **현재상태**(분석): 게이트웨이 토큰 텔레메트리 0 고정, cost 모니터링·캐싱 부재.
- **권고**: 토큰 텔레메트리 실측, 저비용 모델 분기·캐싱, 비용 대시보드.
- **현재상태**: 1차로 턴별 provider/model/tokens/cost 저장 경로와 `GET /admin/usage`, 관리자 비용 대시보드를 연결했다. `ADMIN_USAGE_BUDGET_USD` 기준 예산 상태(ok/warn/exceeded)도 응답/UI에 표시한다. DB 미가용 dev는 runtime store fallback, prod는 fail-closed다. 캐싱·저비용 모델 분기는 아직 없다.
- **권고**: 캐싱, 저비용 모델 분기, 장기 비용 추이/한도 정책.
### [low] L1. 기술스택 신청서-구현 불일치 및 단기일정 산출물 압박
- **근거**: doc4(Spring Boot 3/Node.js·TimescaleDB vs 실제 FastAPI/Python), doc3(20주)
@ -110,17 +110,18 @@
## 4. 즉시 착수 가능 vs 소유자 결정/외부 의존
### A. 즉시 착수 가능(코드/스키마 내부, 외부 합의 불요)
- C2(2),(4): escalate 시 safety_events insert, prepare_turn에서 ideation_observed 전달 — 배선 수정.
- C3: theory 인자 추가 + Session.tsx 하드코딩 제거 + 프론트 선택 UI 골격.
- H2: eval_hook을 turn 파이프라인에 주입(이미 정의·export됨) — 배선.
- C1 1차 완료: `caseWorksheet` 응답 구조와 리뷰 화면 read-only 워크시트 카드. 후속은 저장형 입력 폼·임상 루브릭·AI 추출/채점.
- 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 로드 경로.
- H4(부분): Presidio 한국어 설정·룰, llm_call_log 적재, 마스킹 미들웨어 하드게이트.
- M1(부분), M2, X1, X2: 메타 이벤트 보존, build_recall_context 구현, ds.* read/write·export, 토큰 텔레메트리.
- H4(부분): 한국어 이름/기관 NER, 운영 로그인 `/turn` 후 llm_call_log row 실측, 동의 게이트.
- X1: dry-run JSONL export·PII scan·IAA 계산 1차 완료. `ds.*` write는 `--write-dataset` 명시 시에만 수행한다. X2 비용 관측·budget alert 1차는 완료했고 캐싱·저비용 모델 분기는 후속. M1/M2도 1차 구현 완료, provider 기반 비언어 감지와 case_digest/pinned_fact 실적재는 후속.
### B. 소유자 결정 / 외부(임상팀·기관) 의존
- **임상팀(구훈정·어유경) 산출물**: C1 워크시트 항목·채점 루브릭, C3 CBT 이론 콘텐츠·프롬프트 체인, C2 위기 스크립트·서약 문안, H2 골든셋 코딩 — 콘텐츠는 doc3 R&R상 임상팀 소유. 코드는 '편집 가능 구조'만 선제 구축.
- **임상팀(구훈정·어유경) 산출물**: C1 채점 루브릭·항목 확정, C3 CBT 이론 콘텐츠·프롬프트 체인, C2 위기 스크립트·서약 문안, H2 골든셋 코딩 — 콘텐츠는 doc3 R&R상 임상팀 소유. 코드는 구조를 선제 구축하되 임상 문안과 평가기준은 외부 정의로 받는다.
- **소유자 평가설계 결정**: H1 실험/통제군 배정·3척도 문항·50분 흐름. κ/ICC·환각률 목표는 doc4 미명시 → 평가설계 문서 확정.
- **기관(한신 IT) 의존**: M3 SSO IdP 프로토콜·test tenant·claim 스키마, SAML 인증서, 거버넌스 증거.
- **기관(한신 IT) 의존**: M3 SSO IdP 프로토콜·test tenant·claim 스키마, SAML 인증서, deprovisioning 거버넌스 증거.
- **거버넌스 결정**: L1 스택 정합성 처리 방향, 저작권 등재 문서화 수준, IP 협의.
## 5. 미해결·판독 한계 (quality_flags)
@ -129,4 +130,4 @@
- OCR 잔재(doc1 '괴게 씩씩'≈'되게 씩씩', doc5 따옴표 깨짐·오탈자). 의미는 시각 보정했으나 일부 표현 불확실.
- doc4 KPI 범위 주의: κ/ICC·환각률은 신청서 본문 미명시('정량 신뢰도 확보' 수준) — 계약 확정 지표로 단정 금지.
- doc4/doc3 행정 불일치: 참여교수 1명 vs 구훈정·어유경 2명 병기, 서식7 연도 '2025' 오기, 연구책임자 표기 불일치(류승택/구훈정), 트웬티온스 4인 성명 공란.
- '현재상태'는 grep/코드 사실 기반 적대적 비평 인용 — 위 검증 메모 5건은 작성자 직접 재확인. 그 외는 착수 전 코드 1차 재확인 권고.
- '현재상태'는 grep/코드 사실 기반 적대적 비평 인용에서 시작했으나, C1·C2·C3·H2·M3은 1차 구현 후 재검증 완료했다. H3은 작성자 직접 재확인 기준이며, 그 외는 착수 전 코드 1차 재확인 권고.

View file

@ -6,6 +6,8 @@
- 현재 머신에서 `tailscale serve` 루트 HTTPS가 `http://127.0.0.1:5173`으로 연결된다.
- Tailnet 런타임은 API를 `8010`에 띄우고, Vite proxy가 `VITE_API_PROXY_TARGET=http://127.0.0.1:8010`을 보도록 분리했다. 기존 8000 reloader 잔류와 충돌하지 않게 하기 위해서다.
- Tailnet dev-login은 `AUTH_DEV_LOGIN_EXTRA_ORIGINS=["https://alpaca-home.taile93291.ts.net"]`가 있을 때만 dev 환경에서 열린다. prod에서는 계속 차단된다.
- Tailnet dev-origin에서는 Google OAuth 버튼을 비활성화하고, 직접 `/api/auth/login?provider=google`을 열어도 `local_oauth_unavailable` 안내로 되돌린다. 현재 redirect URI가 `https://api-vignette.chanpaca.net/auth/callback`로 고정되어 있어 Tailnet API가 만든 OAuth state를 public API callback이 검증할 수 없기 때문이다. 원격 수동검증은 `로컬 테스트 계정으로 계속`을 사용한다.
- Vite proxy는 원래 프론트 `Host`/proto를 `X-Forwarded-Host`/`X-Forwarded-Proto`로 API에 넘긴다. 이 헤더가 없으면 dev OAuth 차단 redirect가 public 로그인으로 새는 회귀가 생긴다.
- `scripts/dev-up.ps1`는 API 포트와 `VITE_API_PROXY_TARGET`을 같은 값으로 강제한다. Vite가 8010을 보는데 API가 8000/8002로 떠서 `/api/auth/config`가 500을 내던 회귀를 막기 위한 조치다.
## 검증
@ -15,7 +17,10 @@
- `GET https://alpaca-home.taile93291.ts.net/login``200`
- `GET https://alpaca-home.taile93291.ts.net/api/auth/config``dev_login_enabled: true`
- `POST https://alpaca-home.taile93291.ts.net/api/auth/dev-login` + cookie 재사용 `GET /api/auth/me``200`
- `GET https://alpaca-home.taile93291.ts.net/api/auth/login?provider=google&next=%2Flearn``302 https://alpaca-home.taile93291.ts.net/login?oauth=local_oauth_unavailable`
- Playwright DOM 확인: `https://alpaca-home.taile93291.ts.net/login``로컬 테스트`, `학습자`, `교수자`, `관리자`, `로컬 테스트 계정으로 계속`이 표시된다.
- Tailnet auth readiness E2E: `PLAYWRIGHT_BASE_URL=https://alpaca-home.taile93291.ts.net PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/auth.spec.ts --project=chromium-desktop --grep "shows Google OAuth readiness"``1 passed`
- Tailnet dev-login E2E: `PLAYWRIGHT_BASE_URL=https://alpaca-home.taile93291.ts.net PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/auth.spec.ts --project=chromium-desktop --grep "shows Google OAuth readiness|logs in locally"``2 passed`
- 로컬 리스너:
- gateway `127.0.0.1:9099`
- Tailnet API `127.0.0.1:8010`
@ -36,7 +41,7 @@
- 현재 `vnet.18ka.net``api-vnet.18ka.net`은 DNS가 없다.
- 현재 cloudflared 인증 컨텍스트는 `18ka.net` zone을 관리하지 못한다. `cloudflared tunnel route dns vignette vnet.18ka.net` 실행 시 원하는 `18ka.net`이 아니라 `vnet.18ka.net.chanpaca.net` 레코드를 만들었다.
- prod API는 DB 연결이 없으면 fail-closed로 종료한다. 현재 `127.0.0.1:55432` Postgres가 꺼져 있어 `http://127.0.0.1:8001/health`가 뜨지 않는다.
- prod API는 현재 `127.0.0.1:8001`에서 `db:true`, `engine:true`로 건강하다. 다만 `api-vnet.18ka.net` DNS가 없어 외부 vnet 도메인으로는 아직 도달할 수 없다.
- Google OAuth Console에도 `https://api-vnet.18ka.net/auth/callback`을 Authorized redirect URI로 추가해야 실제 Google 로그인이 완료된다.
## 재기동 명령