From 709baadd012268e62d286d3885b96829aa3e3a86 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Thu, 25 Jun 2026 21:16:45 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20Vignette=20=EB=AA=A8=EB=85=B8=EB=A0=88?= =?UTF-8?q?=ED=8F=AC=20=EC=B4=88=EA=B8=B0=20=EC=8A=A4=EC=BA=90=ED=8F=B4?= =?UTF-8?q?=EB=93=9C=20+=20=EC=84=A4=EA=B3=84=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=9D=B4=EA=B4=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - apps/web(React) · apps/api(FastAPI) · infra(Docker) · docs(설계 SoT) - 마스터플랜·적대검증·메모리설계·디자인컨셉 docs 이관 - 미성년 사례데이터는 .gitignore로 제외(개인정보 보호) --- .env.example | 5 + .gitignore | 41 ++ README.md | 46 ++ apps/api/README.md | 3 + apps/web/README.md | 4 + docs/DESIGN_CONCEPT.md | 942 ++++++++++++++++++++++++ docs/MASTERPLAN.md | 324 ++++++++ docs/MEMORY_KNOWLEDGE_PERSONA_DESIGN.md | 602 +++++++++++++++ docs/mockups/session.html | 486 ++++++++++++ docs/redteam/MASTERPLAN_REVISIONS.md | 335 +++++++++ docs/redteam/REDTEAM_FINDINGS.md | 296 ++++++++ infra/README.md | 3 + 12 files changed, 3087 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 apps/api/README.md create mode 100644 apps/web/README.md create mode 100644 docs/DESIGN_CONCEPT.md create mode 100644 docs/MASTERPLAN.md create mode 100644 docs/MEMORY_KNOWLEDGE_PERSONA_DESIGN.md create mode 100644 docs/mockups/session.html create mode 100644 docs/redteam/MASTERPLAN_REVISIONS.md create mode 100644 docs/redteam/REDTEAM_FINDINGS.md create mode 100644 infra/README.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8797344 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +ANTHROPIC_API_KEY= +OPENAI_API_KEY= +DATABASE_URL=postgresql://user:pass@nas:5432/vignette +CLAUDE_P_PATH=claude +GLM_OR_FALLBACK_MODEL= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be6ae78 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# === 민감 데이터 (절대 커밋 금지) === +# 미성년·자살사고 포함 상담 사례 원본 — 개인정보·동의 미확보. repo에 올리지 않는다. +data/raw/ +*.hwp +*.hwpx +**/축어록* +**/*사례*.docx + +# === 시크릿 === +.env +.env.* +!.env.example +*-key.txt +*.pem +secrets/ +.glassdeck-auth.env + +# === Node / 프론트 === +node_modules/ +dist/ +build/ +.next/ +.turbo/ +*.log +.pnpm-store/ + +# === Python / 백엔드 === +__pycache__/ +*.py[cod] +.venv/ +venv/ +*.egg-info/ +.pytest_cache/ +.ruff_cache/ + +# === Docker / 런타임 === +*.pid +.DS_Store +.idea/ +.vscode/ +postgres-data/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..295e7fb --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Vignette + +> AI 심리상담 시뮬레이션 훈련 플랫폼 — 한신대 SW중심대학 산학협력 (트웬티온스) + +상담 수련생이 **가상 내담자 AI**와 음성으로 상담을 연습하고, **백그라운드 평가 AI**가 실시간·회기말 피드백을 준다. "임상 비네트(사례 삽화)"로 안전하게 연습한다는 의미에서 *Vignette*. + +## 구조 (모노레포) + +``` +vignette/ +├ apps/ +│ ├ web/ React 19 프론트엔드 (3역할: 관리자/교수자/학습자, 추후 RN 네이티브) +│ └ api/ FastAPI 백엔드 (엔진 어댑터·상태머신·가드레일·RAG) +├ infra/ Docker Compose (web·api·postgres·voice gateway) +└ docs/ 설계 문서 (SoT) +``` + +## 핵심 설계 (docs/) + +| 문서 | 내용 | +|---|---| +| `docs/MASTERPLAN.md` | 시스템 마스터플랜 (아키텍처·로드맵·스택) | +| `docs/redteam/REDTEAM_FINDINGS.md` | 적대검증 40결함 | +| `docs/redteam/MASTERPLAN_REVISIONS.md` | 재설계 패치 (claude -p 1급 엔진 복원 등) | +| `docs/MEMORY_KNOWLEDGE_PERSONA_DESIGN.md` | 메모리·지식·페르소나 (회기 간 연속성) | +| `docs/DESIGN_CONCEPT.md` | 디자인 컨셉 (토큰·아바타·화면) | +| `docs/mockups/` | 화면 레이아웃 시안 (HTML) | + +## 확정 스택 + +- **엔진**: 로컬 Opus 4.8 `claude -p` 상주 멀티턴 풀(`--input-format stream-json`), Anthropic Messages API 폴백 +- **프론트**: React 19 + SSE (pnpm/Turborepo 모노레포, 추후 React Native) +- **백엔드**: FastAPI + SSE 스트리밍 +- **DB**: NAS PostgreSQL 16 + pgvector +- **RAG**: BGE-M3 + 하이브리드 + Contextual Retrieval + BGE-reranker-v2-m3 +- **음성**: OpenAI 캐스케이드(STT→LLM→TTS), 멀티보이스 + 페르소나 +- **인증**: OAuth 2.1 (BFF, 3역할 RBAC, visible_to 정보비대칭) +- **배포**: Docker Compose, chanpaca.net 외부노출(교수 테스트) + +## 3-AI + +① 심리상담사 AI(선택) ② 가상 내담자 AI ③ 백그라운드 평가/교수 AI — 정보 비대칭을 DB `visible_to`가 강제. + +## 상태 + +설계 완료, 구현 착수 단계 (Phase 0 기반정렬 → P1 텍스트 상담 MVP → P2 음성+3역할 → P3 파일럿). diff --git a/apps/api/README.md b/apps/api/README.md new file mode 100644 index 0000000..0850fd0 --- /dev/null +++ b/apps/api/README.md @@ -0,0 +1,3 @@ +# apps/api + +FastAPI 백엔드 (Vignette). 엔진 어댑터(claude -p 상주풀)·상태머신·가드레일·RAG·RBAC. diff --git a/apps/web/README.md b/apps/web/README.md new file mode 100644 index 0000000..1202854 --- /dev/null +++ b/apps/web/README.md @@ -0,0 +1,4 @@ +# apps/web + +React 19 프론트엔드 (Vignette). 3역할 페이지 + 상담 세션 UI + 아바타. +스택: React 19 + Vite + SSE. 추후 React Native 공유 코어. diff --git a/docs/DESIGN_CONCEPT.md b/docs/DESIGN_CONCEPT.md new file mode 100644 index 0000000..85110c8 --- /dev/null +++ b/docs/DESIGN_CONCEPT.md @@ -0,0 +1,942 @@ +--- +date: 2026-06-25 +type: design-concept +project: 한신대학교 AI 심리상담 시뮬레이션 훈련 플랫폼 +tags: + - 디자인컨셉 + - 디자인시스템 + - UX + - 아바타 + - 한신대 +status: 통합본 v1 +--- + +# 한신대 AI 심리상담 시뮬레이션 플랫폼 — 통합 디자인 컨셉 문서 + +> **이 문서 하나로 사이트를 만들 수 있다.** 디자인 철학 → 디자인 시스템 토큰(실측값) → 가상 내담자 아바타(상세) → 상담 세션 화면 → 3역할 대시보드 → 컴포넌트·모션 → React 구현 노트 → 안티패턴 체크리스트까지, 추상적 형용사 없이 실제 hex·px·컴포넌트·인터랙션으로 적었다. +> +> 5개 영역 설계([design-language] / [design-system] / [avatar] / [session-ux] / [role-dashboards])를 단일 기준 문서로 종합한 것이다. 토큰 충돌 지점은 본 문서가 **단일 진실원본(SSOT)**으로 일원화했다. +> +> **재사용 자산 연계**: 윤찬 기존 디자인 시스템(`Resources/50_Tech_Dev/디자인/slide-base.css`)과 동일한 토큰 철학(무채 골격 + accent 교체). slide-base 표의 "한신대/의료" 빈 행을 본 문서의 `--accent`로 채운다. + +--- + +## 0. 한 문장 컨셉과 SSOT 결정 + +### 0.1 컨셉 + +**"불안한 사람을 다루는 사람을 위한, 차분한 임상 도구."** + +상담 수련생(학습자)이 가상 내담자 AI와 음성으로 대면하고, 뒤에서 평가 AI가 조용히 관찰한다. 이 화면은 **두 종류의 신뢰**를 동시에 줘야 한다. + +1. **임상적 신뢰감** — 학습자가 "이건 진지한 임상 훈련 장비다"라고 느낀다. +2. **교육적 따뜻함** — 연습 중 실수해도 안전하다고 느낀다. + +자극적·게임적이면 안 되고, 차갑고 행정적이어도 안 된다. 정답은 그 사이의 **"조용한 따뜻함(quiet warmth)"** — 스펙트럼 위 **65% 임상 : 35% 따뜻함** 지점이다. + +> 화면은 "상담실"이지 "대시보드"가 아니다. 평가 정보는 상담실 벽에 걸린 은은한 시계처럼 곁에 있되, 내담자의 얼굴에서 시선을 빼앗지 않는다. + +### 0.2 토큰 SSOT 결정 (5개 설계 영역의 색 통일) + +5개 영역이 각각 미세하게 다른 그린(`#5B7A6B` / `#3E7A6E` / `#4F6B5D` / `#0E7C7B` / `#0F766E`)을 제안했다. 본 문서는 아래로 **확정 일원화**한다. + +| 용도 | 확정값 | 근거 | +|---|---|---| +| **메인 accent (전역 기본)** | `--accent: #3E7A6E` (세이지-틸) | 저채도·차분, 흰 배경 대비 약 5.0:1 (WCAG AA 통과). 흔한 메디컬 블루/형광 틸 회피. slide-base의 `#0F766E`보다 한 톤 더 낮춤. | +| **역할별 보조 accent** | 학습자 `#3E7A6E` / 교수자 `#3A5BA0` / 관리자 `#5B5F6B` | 같은 골격, accent 한 토큰만 교체. 모두 저채도. | +| **보조 온기 accent** | `--clay: #B0735C` (테라코타) | 양산형 메디컬 블루를 깨는 "사람이 만든 교육 도구" 온기. 저채도로만, 화면당 1곳. | + +이유: **세이지-틸**은 임상적 차분함(자연·회복·안정)과 따뜻함(흙·식물)을 동시에 품으면서, 한국 상담/심리 분야에서 과용되지 않아 차별화된다. 의료 블루는 흔하고 차갑다. + +--- + +## 1. 핵심 디자인 원칙 (신성불가침 5개) + +### 원칙 1 — One Screen, One Intent (한 화면 한 의도) +상담 시뮬레이션은 본질적으로 집중을 요구한다. 학습자가 가상 내담자의 말에 귀 기울이는 순간 화면에 버튼 12개·카드 9개가 깔려 있으면 안 된다. 모든 화면은 그 순간의 **단 하나의 주된 행동**(말하기 / 듣기 / 피드백 읽기 / 세션 시작)을 중심으로 설계한다. 부차 정보는 위계 아래로. +- *적용*: 세션 진행 화면 = 중앙에 내담자 발화 + 음성 비주얼 하나. 평가 피드백은 화면 가장자리 차분한 사이드 패널로, 절대 모달로 튀지 않는다. + +### 원칙 2 — 위계는 색 블록이 아니라 타이포 weight + 여백으로 +Stripe·Linear의 공통점: 테두리·색 박스로 구역을 나누지 않고 **여백과 글자 굵기로** 나눈다. 강조는 ①font-weight(400→600) ②accent 텍스트/언더라인 ③상단 kicker(소제목) ④accent dot 으로만. **border-left 강조선·좌측 사이드바 강조줄·색 박스 카드 격자 절대 금지.** +- *적용*: 평가 피드백 "공감 반영: 우수"를 초록 배지로 칠하지 않는다. kicker("공감 반영") + 본문 한 줄 + 미세한 accent dot 한 점으로 위계를 만든다. + +### 원칙 3 — 임상적 차분함 = 채도를 죽인 중성 베이스 + 절제된 단일 악센트 +Calm/Headspace에서 배울 건 파스텔 그라데이션이 아니라 **고채도·고대비를 의도적으로 피하는 절제**다. 베이스는 따뜻한 회백(off-white)·차분한 슬레이트. 악센트는 **세이지-틸 단 하나**를 화면당 1군데만 배급(Linear의 "accent rationing"). 빨강·주황 경고색은 진짜 오류/위험에만. +- *근거*: 불안한 내담자를 다루는 훈련 도구이므로 자극적 색은 학습자 정서·임상 톤 양쪽에 해롭다. 녹색은 우울·불안·스트레스 점수를 가장 많이 낮춘 색이며 "go/긍정" 신호로도 조용히 작동한다. + +### 원칙 4 — 따뜻함은 "둥근 카드"가 아니라 "휴머니스트 타이포 + 따뜻한 중성톤 + 넉넉한 여백" +따뜻함을 둥근 모서리·말랑한 일러스트로 내면 임상 신뢰가 깨진다(애들 앱처럼 보임). 대신 **헤드라인에 휴머니스트 세리프 한 톤 옵션**(therapy 웹의 정설: serif=신뢰·인간미), 본문은 깔끔한 산세리프. 순백(#FFF) 대신 따뜻한 종이색, 순흑 대신 잉크 네이비-그린. 이 미세한 온기가 "행정 시스템"과 "사람을 위한 도구"를 가른다. +- *주의*: 한글 본문은 Pretendard 단일 패밀리를 기본으로 한다(국문 상담 도메인 가독·중립). 세리프 헤드라인은 랜딩·표지 같은 희소 지점에서만 선택 적용(본명조). UI 전반은 산세리프 weight 위계로 통일. + +### 원칙 5 — 평가 AI는 판사가 아니라 조용한 멘토 (Non-judgmental surfacing) +실시간 피드백이 빨간 X·점수 폭격·"틀렸습니다"로 뜨면 학습자는 위축된다. 피드백은 **관찰("이 지점에서 내담자가 방어적으로 반응했어요") → 제안("열린 질문으로 바꿔볼까요?")** 순서의 **서술형·저채도·비모달**로. 점수는 세션이 끝난 뒤 회고 화면에서만, 절대값이 아니라 성장 추세로. + +--- + +## 2. 무드 좌표 (임상 ↔ 따뜻함 균형) + +양 극단(차가운 EHR / 말랑한 명상앱) 사이, **65% 임상 : 35% 따뜻함**. + +| 축 | 임상 신뢰(차가움) ← | **→ 우리 좌표 ←** | → 따뜻함(부드러움) | +|---|---|---|---| +| 베이스색 | 순백·차가운 그레이 | **따뜻한 off-white `#FBFAF8` + 차분한 슬레이트** | 크림·파스텔 | +| 악센트 | 임상 블루 | **세이지-틸 `#3E7A6E`** | 코랄·더스티 로즈 | +| 헤드라인 | 기하 산세리프 | **산세리프 weight 위계 (희소 지점만 본명조 옵션)** | 손글씨·라운드 | +| 모서리 | 직각 | **6~8px (거의 직각, 미세 라운드)** | 16px+ 둥근 | +| 그림자 | 없음(헤어라인) | **1px 헤어라인 + 아주 약한 그림자** | 부드러운 큰 blur | +| 모션 | 즉각·없음 | **200~320ms ease-out, 절제** | 느린 호흡 애니메이션 | + +> 단 하나의 예외 — 음성 듣기 중 마이크/오브의 **느린 호흡 펄스**. 이건 "지금 듣고 있다"는 정서적 안심 신호라 의도적으로 명상앱 호흡 모션을 차용한다. + +--- + +## 3. 디자인 시스템 토큰 (그대로 `:root`에 붙여 쓰는 실측값) + +CSS 변수는 primitive(원시값) → semantic(의미 별칭) 2계층. 컴포넌트는 semantic만 참조 → 다크모드 1곳에서 스왑. + +### 3.1 중성 베이스 (Neutral) — 골격 + +순백/순흑 금지. 따뜻한 종이색 + 잉크 네이비-그린. + +```css +:root { + /* ── Primitive: Neutral (따뜻한 종이 + 쿨 슬레이트) ── */ + --paper: #FBFAF8; /* 본문 배경: 따뜻한 종이색 (순백 아님) */ + --paper-2: #F4F2EE; /* 카드/패널 미세 단차 (테두리 대신 톤차) */ + --surface: #FFFFFF; /* 카드 표면 (캔버스가 종이색이라 분리됨) */ + --neutral-100: #ECEFF1; /* 표면 hover / subtle fill */ + --neutral-150: #E5E1DA; /* 카드 보더 baseline = hairline */ + --neutral-200: #D5DBDE; /* divider 진함 */ + --neutral-400: #98A4A9; /* placeholder / disabled */ + + --ink: #1C2A2A; /* 본문 텍스트: 잉크 네이비-그린 (순흑 아님) */ + --ink-2: #5A6663; /* 보조 텍스트: 슬레이트 */ + --ink-3: #93A09C; /* 캡션/메타 */ + --hair: #E5E1DA; /* 1px 헤어라인 (구역 분리는 이걸로) */ +} +``` + +### 3.2 악센트 — 1주 + 1보조 (저채도) + +```css +:root { + /* ── Sage-Teal (주 악센트 = 상담/신뢰), 화면당 1곳 배급 ── */ + --accent: #3E7A6E; /* ★ 메인 악센트 (버튼/링크/포커스). 흰 배경 대비 5.0:1 AA */ + --accent-deep: #316257; /* hover / 진한 강조 텍스트 */ + --accent-tint: #EEF4F2; /* 악센트 배경 틴트 (콜아웃·선택·active) */ + --accent-bright:#5F968B; /* dot·진행바 fill (화면 전용) */ + + /* ── Terracotta (보조 악센트 = 학습/온기), 저채도, 화면당 1곳 ── */ + --clay: #B0735C; /* ★ 보조 악센트 (학습자 온기 포인트, 내담자 측 신호) */ + --clay-deep: #94604C; + --clay-tint: #F6F0EC; +} +``` + +`--accent #3E7A6E`는 흰 배경 명도대비 약 5.0:1 → 본문 링크/버튼 텍스트 WCAG AA 통과. **2색을 동시에 큰 면적으로 쓰지 않는다** — sage가 전역 기본, clay는 학습자 모드/온기 포인트에서만 점·언더라인·소형 fill로. + +### 3.3 의미색 (Semantic feedback) — solid + tint 페어 + +각 의미색은 아이콘/텍스트용 solid + 배경용 tint. tint 위 text는 모두 AA(4.5:1↑). + +```css +:root { + /* 긍정 = 녹색 (상담 진전·라포 형성·가드레일 통과·잘함) */ + --pos-text: #2C6E49; --pos-solid: #3B8C5E; --pos-tint: #E8F3EC; + + /* 주의 = 앰버 (라포 약화·과도한 침묵·페이싱) — 빨강 대신 머스터드 */ + --warn-text: #8A5A00; --warn-solid:#C28A2C; --warn-tint: #FAF1E0; + + /* 위험 = 적색 (가드레일 위반·세션 강제중단·임상적 위급) — 오직 진짜 위급만 */ + --crit-text: #A8352C; --crit-solid:#C4453A; --crit-tint: #FAEAE8; + + /* 정보 = 슬레이트블루 (평가 AI 피드백·시스템 안내, 중립) */ + --info-text: #2E5A7A; --info-solid:#3C7299; --info-tint: #EAF1F6; + + --neutral-sig:#8895A2; /* 데이터 없음·미평가 */ +} +``` + +**핵심 원칙: 평가 AI의 실시간 피드백 기본 톤은 `info`(슬레이트블루) 또는 `pos`(녹색)** 으로 — 학습자가 비난받는 느낌을 받지 않게. 빨강은 코칭 피드백에 절대 쓰지 않고, 가드레일/위급에만. + +### 3.4 시맨틱 별칭 (라이트 모드) + +컴포넌트는 primitive를 직접 부르지 않고 이 별칭만 참조. + +```css +:root { + --bg-app: var(--paper); /* 앱 캔버스 */ + --bg-surface: var(--surface); /* 카드/패널 */ + --bg-surface-2: var(--paper-2); /* 중첩 표면/입력 */ + --bg-tint: var(--accent-tint); /* 선택·강조 배경 */ + --bg-stage: #1E2724; /* 내담자 아바타 영역 (깊은 차콜그린, 시선 집중) */ + + --text-strong: var(--ink); /* 제목 */ + --text-body: var(--ink-2); /* 본문 보조 */ + --text-muted: var(--ink-3); /* 캡션 */ + --text-on-accent:#FBFAF8; /* accent 버튼 위 텍스트 */ + + --border-subtle: var(--neutral-150); + --border-strong: var(--neutral-200); + --border-focus: var(--accent); + --focus-ring: color-mix(in srgb, var(--accent) 45%, transparent); +} +``` + +### 3.5 다크 모드 (gloom 회피 — 충분한 밝은 면 + 절제된 악센트) + +배경은 한색 슬레이트(순흑 아님). 악센트는 명도를 올려 가독 확보. + +```css +[data-theme="dark"] { + --bg-app: #131A1E; --bg-surface: #1A2429; --bg-surface-2: #222E34; + --bg-tint: #1E2E2A; --bg-stage: #0E1614; + --text-strong: #EAF0F1; --text-body: #C4CFD3; --text-muted: #88969C; + --text-on-accent:#0F1A17; + --border-subtle: #2A363C; --border-strong: #38454C; --border-focus: #6FB3A4; + --accent: #6FB3A4; --accent-deep: #84C2B4; --accent-tint: #1E2E2A; + --clay: #CC8F77; + + --pos-text: #7FD0A0; --pos-solid: #5FB682; --pos-tint: #16271D; + --warn-text: #E2B566; --warn-solid:#D4A24A; --warn-tint: #2A2415; + --crit-text: #E89086; --crit-solid:#D86459; --crit-tint: #2A1715; + --info-text: #79B0D4; --info-solid:#5C97BE; --info-tint: #15222C; +} +``` + +### 3.6 타이포그래피 + +| 역할 | 폰트 | 이유 | +|---|---|---| +| **본문/UI 기본** | **Pretendard Variable** | 한글 UI 사실상 표준. 9 weight 가변, 숫자 정렬 우수. 임상 데이터·평가 점수 표기에 깔끔. | +| **숫자·타임코드** | Pretendard + `tabular-nums` | 세션 타이머·점수는 정렬 필요 → `font-variant-numeric: tabular-nums`. | +| (옵션) 랜딩 헤드라인 | Noto Serif KR (본명조) | 신뢰·인간미. **희소 지점만**(표지·히어로). UI 전반은 산세리프. | + +```css +:root { + --font-sans: "Pretendard Variable", Pretendard, -apple-system, + "Apple SD Gothic Neo", "Noto Sans KR", system-ui, sans-serif; + --font-serif:"Noto Serif KR", "Source Serif 4", serif; /* 랜딩 헤드만 */ + --font-num: "Pretendard Variable", "JetBrains Mono", ui-monospace, monospace; +} +body { font-family: var(--font-sans); -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; } +.tabular { font-variant-numeric: tabular-nums; letter-spacing: -0.01em; } +``` + +CDN: `https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/variable/pretendardvariable.min.css` + +**타입 스케일** (기준 본문 16px / 1.6, 한글 자간 -0.01~-0.02em. 위계는 크기보다 weight로): + +| 토큰 | px / rem | line-height | weight | letter-spacing | 용도 | +|---|---|---|---|---|---| +| `--fs-display` | 40 / 2.5rem | 1.2 | 700 | -0.02em | 표지·랜딩 대제목 (희소) | +| `--fs-h1` | 30 / 1.875rem | 1.3 | 700 | -0.02em | 페이지 제목 | +| `--fs-h2` | 24 / 1.5rem | 1.35 | 600 | -0.015em | 섹션 제목 | +| `--fs-h3` | 20 / 1.25rem | 1.4 | 600 | -0.01em | 카드/패널 제목 | +| `--fs-lead` | 18 / 1.125rem | 1.6 | 400 | -0.01em | 리드 문장 | +| `--fs-body` | 16 / 1rem | 1.6 | 400 | -0.01em | 본문 기본 | +| `--fs-sm` | 14 / 0.875rem | 1.55 | 400 | 0 | 보조·폼 라벨 | +| `--fs-xs` | 13 / 0.8125rem | 1.5 | 500 | 0.01em | 캡션·메타·배지 | +| `--fs-kicker` | 12 / 0.75rem | 1.4 | 600 | 0.08em | 상단 kicker 소제목 | + +**강조 방식(철칙):** 색 블록·border-left 금지. ① weight 400→600 ② accent 텍스트 컬러 ③ 은은한 `--accent-tint` 배경 틴트 ④ 상단 kicker(작은 도트 동반). kicker가 양산형 좌측바를 대체하는 위계 신호다. + +### 3.7 스페이싱 (8pt 기반, 넉넉하게) + +```css +:root { + --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; + --sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px; --sp-9: 96px; +} +``` + +원칙: **컴포넌트 내부 패딩은 8의 배수, 섹션 간 간격은 48~96px 넉넉히.** 불안 완화의 핵심은 여백. 카드 내부 패딩 기본 `--sp-5`(24px), 패널 `--sp-6`(32px), 읽기 영역 max-width 640~680px. + +### 3.8 그리드·형태 + +```css +:root { + --maxw: 1200px; /* 대시보드 콘텐츠 최대폭 */ + --maxw-read: 680px; /* 읽기 영역 */ + --nav-w: 240px; /* 좌측 네비 펼침 */ + --nav-w-collapsed: 72px; /* 좌측 네비 아이콘 only */ + --radius: 8px; /* 카드/버튼/입력 (거의 직각) */ + --radius-sm: 6px; /* 입력·칩 */ + --radius-lg: 12px; /* 패널 */ + --gutter: 24px; +} +``` + +- `border-radius`: 카드 8px, 버튼 8px, 입력 6px (거의 직각, 미세 라운드만). **원형은 음성 오브·아바타·dot만 예외.** +- **깊이는 1px 헤어라인(`--hair`) + 화면용 아주 약한 그림자.** glow·gradient-text·큰 blur 금지. + +```css +:root { + --shadow-sm: 0 1px 2px rgba(28,42,42,.05); + --shadow-md: 0 4px 14px rgba(28,42,42,.07); /* 모달·팝오버만 */ +} +``` + +### 3.9 모션 + +```css +:root { + --ease-out: cubic-bezier(.22,.61,.36,1); + --ease-in-out: cubic-bezier(.45,.05,.55,.95); + --dur-fast: 120ms; /* hover·포커스 */ + --dur-base: 200ms; /* 버튼·토글·드롭다운 */ + --dur-slow: 320ms; /* 패널 전환·모달 */ + --dur-breathe: 3200ms; /* 음성 호흡 펄스 */ +} +@media (prefers-reduced-motion: reduce) { + *,*::before,*::after { animation-duration:.01ms!important; transition-duration:.01ms!important; } +} +``` + +원칙: **차분·짧게·자연 이징.** 불안 도구이므로 튀는 스프링/바운스 금지. 화면 전환은 페이드+미세 슬라이드(translateY 8px 이하). 실시간 피드백 칩은 **자리 미리 확보 후 fade**(밀어내기/점프 금지 — 레이아웃 시프트가 불안 유발). `prefers-reduced-motion` 필수 대응. + +--- + +## 4. 가상 내담자 아바타 (이 플랫폼의 심장) + +학습자의 시선이 30~50분간 머무는 단 하나의 오브젝트. 산만하면 라포 연습을 방해하고, 과하면 실제 환자로 오인되며, 빈약하면 몰입이 깨진다. + +### 4.1 추천: "반(半)구상 페르소나 카드 (Semi-figurative Persona Card)" + +사실적 얼굴도, 추상 오브도 아닌 그 사이. **사실성 35~45%의 양식화 반구상 흉상 + 호흡하는 광배(aura)**. + +#### 표현 방식 비교 (왜 이걸 골랐나) + +| 방식 | 몰입 | 제작비용(20주) | 윤리/오인 위험 | 불쾌한 골짜기 | 결론 | +|---|---|---|---|---|---| +| A. 추상 오브 / 음성 비주얼라이저 | 낮음 | 매우 낮음(1주) | 없음 | 없음 | **폴백으로만**(미선택/로딩/에러) | +| **B. 2D 양식화 일러스트(반구상)** | 높음 | 중간(3~6주) | 낮음 | 회피 가능 | **★ 1차 채택** | +| C. 실사 / AI생성 얼굴 | 매우 높음 | 높음 | **높음** | **직격(최악)** | 탈락 | +| D. Live2D / 3D | 매우 높음 | 매우 높음(6~10주+) | 중간 | 리깅 품질 의존 | 2차/장기 후보 | + +- **B를 고른 이유**: 사실성을 의도적으로 35~45%에 고정 → 불쾌한 골짜기 회피. 벡터라 페르소나별 파라미터(피부톤·헤어·눈 형태·연령감)를 **데이터로 찍어내 시스템화 가능**(페르소나 무한 확장). 눈·눈썹·입꼬리·어깨선 4채널만으로 저항/라포/정서를 은은히 표현. 윤찬 미니멀 톤과 정합. +- **C 탈락**: 80% 사실성에서 미세 립싱크 오차가 더 크게 불쾌. 미성년·취약 내담자를 실사로 그리면 "이 아이 실존?" 윤리 문제 + 데이터 편향. +- **A를 폴백으로 흡수**: ElevenLabs Orb 같은 성숙한 React 자산을 페르소나 미선택/로딩/에러 화면에 영구 잔존. + +#### 카드 구조 + +``` +┌─────────────────────────────────────┐ +│ [상단 kicker] 교육용 가상 내담자 │ ← 상시 라벨(오인 차단) +│ │ +│ ╭───────────────╮ │ +│ │ 양식화 흉상 │ │ ← 어깨~머리, 평면 벡터 +│ │ (얼굴 35% 사실) │ │ 배경에 호흡하는 광배 +│ ╰───────────────╯ │ +│ │ +│ 서연 · 17세 · 고2 │ ← 페르소나 메타 1줄 +│ ────────── │ +│ 지금: 머뭇거리며 듣는 중 │ ← 상태 텍스트 1줄 +└─────────────────────────────────────┘ +``` + +핵심: **얼굴은 양식화하되 "사람"으로 읽히고, 절대 "실존 인물"로는 안 읽히게.** 코·주름·모공 없음. 눈·눈썹·입꼬리·어깨 각도가 정서를 나르는 4개 채널. + +### 4.2 음성 발화 중 반응 — 상태 모델 (4-state) + +음성 UI 표준 idle/listening/thinking/speaking을 내담자 맥락으로 재정의. + +| 상태 | 트리거 | 입 | 눈/눈썹 | 광배(aura) | 어깨 호흡 | +|---|---|---|---|---|---| +| **listening (학습자 발화 중)** | 마이크 입력 감지 | 닫힘, 미세 입꼬리 | **학습자 쪽 응시**, 깜빡임 4~6s | 차분 펄스 0.06 opacity, 5.5s | 진폭 1.5px, 4s | +| **thinking (응답 생성 대기)** | STT 종료~TTS 시작 | 살짝 다묾 | 시선 살짝 아래/옆, 깜빡임 느려짐 | 안쪽 수축, 채도 -8% | 정지에 가깝게 | +| **speaking (내담자 발화)** | TTS 오디오 재생 | **립싱크 진폭** | 정면 복귀, 눈썹 정서 반영 | 발화 진폭에 약동기(0.04~0.12) | 발화 리듬따라 미세 | +| **idle (시작 전/일시정지)** | 무입력 | 닫힘 | 천천히 깜빡, 가끔 시선 이동 | 최소 펄스 | 느린 호흡 3.5s | + +### 4.3 립싱크 — "정밀 음소 매칭 안 함"이 정답 + +35% 사실성 얼굴에 정밀 비짐(viseme) 매핑은 오히려 불쾌. **오디오 진폭(RMS) → 입 열림 높이** 1채널 매핑. + +```ts +// Web Audio AnalyserNode → 매 프레임 RMS → 입 SVG path 세로 스케일 +const rms = getRMS(analyser); // 0~1 +const mouthOpen = smooth(rms, 0.18); // 지수평활(튐 방지), τ≈180ms +// +``` + +- 입 모양: 닫힘=얇은 호, 열림=타원. `scaleY`만 변형. **이빨·혀 절대 안 그림.** +- **데드존**: rms < 0.04는 닫힘 고정 → 무음 구간 입 떨림 제거. +- 평활 필수: raw RMS 직결하면 입이 경련함. τ=150~200ms. + +### 4.4 모션 진폭 가이드 (산만함 차단의 핵심) + +| 모션 | 진폭 | 주기/속도 | 비고 | +|---|---|---|---| +| 어깨 호흡(idle) | translateY 1.5~2.5px | 3.5~5.5s ease-in-out 무한 | "있는 줄 모르게" | +| 깜빡임 | scaleY 1→0.08→1 | 120ms, 간격 4~7s 랜덤 | 생체감의 80%가 여기서 | +| 광배 펄스 | opacity ±0.04~0.08 | 5~6s | speaking 때만 오디오 동기 | +| 시선 이동(saccade) | ±3px | 0.3s, 간격 6~10s | thinking에서 빈도↑ | +| 립싱크 | scaleY 0.35~1.0 | 오디오 실시간 | 데드존+평활 | + +> 원칙: **동시에 움직이는 채널 ≤ 2개.** speaking이면 입+광배만. listening이면 깜빡임+호흡만. + +### 4.5 정서 시각화 — 6파라미터 상태머신 + +valence/arousal 축을 아바타 파라미터로 인코딩. **과장 절대 금지**(만화적 눈물·홍조·땀방울 전부 금지). 상담 도구의 정서는 "감지되되 단언되지 않는" 수준. + +| 내담자 정서 | 눈/눈썹 | 입꼬리 | 자세(어깨선) | 광배 색조 | 호흡 | +|---|---|---|---|---|---| +| **중립/라포** | 눈썹 수평, 눈맞춤 | 미세 상향 | 정면, 약간 열림 | 세이지 `#3E7A6E` 4% | 안정 4s | +| **우울(저각성·부정)** | 눈꺼풀 1~2px 하강, 시선 아래 | 수평~미세 하향 | 어깨 안으로·아래로 | 청회색 `#5B6B73` 5% | 느리고 얕게 5.5s | +| **불안(고각성·부정)** | 깜빡임↑(2~4s), saccade↑ | 다묾, 긴장 | 약간 들썩 | 차가운 청 `#3B6E8F` 5%, 빠른 펄스 | 빠르고 얕게 3s | +| **저항/방어** | 시선 회피(옆/아래 고정) | 굳게 닫힘 | 반쯤 돌아선 각도(turn 8°) | 중립 회색, 무펄스 | 정지에 가깝게 | + +상태머신이 `(eyelidDrop, gazeAvert, shoulderTurn, breathRate, auraHue, blinkInterval)` 6개 숫자만 바꾸면 정서 전환. **라포가 쌓이면(세션 진행) 저항 페르소나가 shoulderTurn 8°→0°, gazeAvert→눈맞춤으로 서서히 완화** → 학습자가 "내가 라포를 만들었다"를 시각적으로 체감. **이게 이 도구의 교육적 핵심.** 정서 전환은 0.8~1.2s ease로 천천히, 동시 변하는 파라미터 ≤3개. + +### 4.6 페르소나 파라미터 시스템 (새 페르소나 = 새 숫자 묶음) + +```jsonc +// persona/seoyeon.json — 청소년 페르소나 예시 +{ + "id": "seoyeon", + "label": "서연 · 17세 · 고2", + "appearance": { + "ageBand": "teen", // teen | youngAdult | adult | senior + "skinTone": "#E8C9A8", + "hair": { "style": "long-straight", "color": "#2B2723" }, + "faceRatio": { "eyeSize": 1.15, "jawWidth": 0.92 }, // teen=큰 눈·좁은 턱 + "outfit": "hoodie", // 실루엣만, 디테일 X + "realism": 0.40 // 0.35~0.45 고정(불쾌한 골짜기 회피) + }, + "voice": { "provider": "openai", "preset": "soft-young-fem", "rate": 0.96 }, + "baseAffect": "anxious", // 시작 정서 + "resistance": 0.6, // 0~1, 라포로 감소 + "auraHueShift": -6 +} +``` + +#### 연령대 외형 규칙 + +| ageBand | 눈 크기 | 턱/얼굴 | 실루엣 | 광배 채도 | 음성 톤 | +|---|---|---|---|---|---| +| **teen** | 1.1~1.2× | 좁은 턱·둥근 | 후디·체크 | 약간 높음(생기) | 부드럽고 높은 | +| **youngAdult** | 1.0× | 표준 | 캐주얼 | 중간 | 표준 | +| **adult** | 0.95× | 각진 턱 | 셔츠·니트 | 낮음(차분) | 낮고 안정 | +| **senior** | 0.92× | 부드러운 윤곽 | 단정 | 낮고 따뜻 | 느린 | + +- **미성년 페르소나는 realism을 0.35로 더 낮추고** `교육용 가상 내담자(미성년 시뮬레이션)` 라벨 강화 → 실존 미성년 오인 원천 차단. +- 외형은 인종·특정 외모 stereotype 회피를 위해 **중립 톤 팔레트 3~4종**만 제공(편향 최소화). + +### 4.7 아바타 구현 기술 — SVG 1차, Rive 2차 + +| 기술 | 적합 | 근거 | +|---|---|---| +| **SVG + CSS/Web Animations + Web Audio** | ★ 1차 | 페르소나=벡터 파라미터, 립싱크=RMS 1채널, 의존성 0, React 친화, 미니멀 정합 | +| **Rive (상태머신)** | ★ 2차 고도화 | 상태머신이 .riv에 내장, React는 input만 wiring. Lottie 대비 파일 10~15× 작음 | +| Lottie | 보조 | 정형 애니(로딩·전환)엔 OK, 실시간 오디오 반응엔 부적합 | +| Live2D/3D | 장기 | 표현력 최고지만 20주·시스템화 불리 | + +#### 1차안 컴포넌트 구조 (SVG) + +``` + + ├─ // 배경 광배: radial-gradient, opacity 펄스 + ├─ // 어깨선, shoulderTurn 각도 + ├─ + │ ├─ + │ ├─ // 정서별 각도 2~3px + │ └─ // RMS→scaleY 립싱크 + └─ // "교육용 가상 내담자" 상시 +``` + +```tsx +// 단일 rAF 루프, 의존성 0 +function useAvatarMotion(state, affect, analyser) { + const [frame, setFrame] = useState(initFrame(affect)); + useEffect(() => { + let raf, t0 = performance.now(); + const loop = (t) => { + const dt = (t - t0) / 1000; + const breath = Math.sin(dt * (2*Math.PI/affect.breathPeriod)) * affect.breathAmp; + const mouth = state==='speaking' ? smoothRMS(analyser) : 0; + const blink = blinkSchedule(t, affect.blinkInterval); + setFrame({ breath, mouth, blink, gaze: gazeFor(state) }); + raf = requestAnimationFrame(loop); + }; + raf = requestAnimationFrame(loop); + return () => cancelAnimationFrame(raf); + }, [state, affect, analyser]); + return frame; +} +``` + +- **퍼포먼스**: SVG 변형은 `transform`(GPU 합성)만, layout 트리거 속성(width/top) 금지. 단일 rAF, 60fps 여유. +- **RN 이전 대비**: SVG는 `react-native-svg`, Web Audio는 `react-native-audio-api`로 대체 → 로직 재사용. Rive도 RN 런타임 있음. +- **접근성**: `prefers-reduced-motion` 켜지면 호흡·광배 정지, 상태는 텍스트로만. + +--- + +## 5. 상담 세션 화면 (메인 화면) + +핵심 긴장: **몰입 vs 평가 가시성.** 실시간 피드백이 라포 형성을 방해하면 안 되고, 평가가 사후로만 묶이면 학습 효과가 떨어진다. 해법 = **2단(two-tier) 피드백 모델** — 실시간은 "신호(signal)" 수준, 정밀 평가는 "회기말 리뷰"로. + +### 5.1 3-column 골격 (데스크탑 ≥1280px) + +``` +┌──────────────────────────────────────────────────────────────────────────┐ +│ 상단 바 (56px) 좌:세션 제목/시나리오 중앙:회기 단계 우:경과시간·종료 │ +├──────────────────┬──────────────────────────────────┬──────────────────────┤ +│ LEFT (320px) │ CENTER (flexible) │ RIGHT (300px) │ +│ ───────────── │ ───────────────── │ ───────────── │ +│ 회기 단계 트랙커 │ 내담자 아바타 / 음성 STAGE │ 피드백 신호 패널 │ +│ (라포→탐색→ │ (어두운 배경, 중앙 집중) │ (은은한, 평시 접힘) │ +│ 개입→정리) │ │ │ +│ 내담자 컨텍스트 │ [ 아바타 + 음성 오브 ] │ ── 내담자 상태 ── │ +│ 카드 │ │ ── 라이브 신호 ── │ +│ │ 실시간 자막 (아래→위 흐름) │ (최대 1개, 페이드) │ +│ │ │ ── 셀프 노트 ── │ +├──────────────────┴──────────────────────────────────┴──────────────────────┤ +│ 하단 컨트롤 바 (80px) 마이크 · 일시정지 · [몰입│은은│코칭] · (밀어서)종료 │ +└──────────────────────────────────────────────────────────────────────────┘ +``` + +**비율 의도: 시각적 무게 = 주의 배분.** 내담자 stage 60%, 평가 패널 15%(평시 대부분 접힘). 화면은 대시보드가 아니라 상담실이다. + +**반응형:** +- 태블릿(768~1279px): RIGHT 패널이 오버레이 drawer(평시 숨김, 우상단 신호 도트만). LEFT 280px. +- 모바일/RN(<768px): 단일 컬럼. stage 상단 고정(40vh), 자막 스크롤, 단계는 상단 가는 진행선, 평가는 하단 시트(bottom sheet). 컨트롤 바 하단 고정. + +### 5.2 중앙 STAGE — 음성 오브 (Voice Orb, 4상태) + +자극적 막대 파형 대신 **차분한 호흡(breathing) 애니메이션**. 아바타(원형 220px) 둘레 오브 링. + +| 상태 | 시각 | 색 | 모션 | +|---|---|---|---| +| **내담자 말하는 중** | 따뜻한 링, 음량 따라 미세 진동 | `--clay` glow, 두께 4px | 음량 연동 amplitude(0.5~3px), 0% 형광 | +| **학습자 말하는 중** | 세이지 링, 호흡 펄스 | `--accent` glow | 1.4s ease breathing scale 1.0→1.04 | +| **대기/침묵(thinking)** | 가는 점선 매우 느리게 회전 | `--ink-3` | 8s/회전, 거의 안 보일 정도 | +| **일시정지** | 반투명 오버레이 + "일시정지" | `--bg-stage` 60% | 정지 | + +```css +.orb { + position: absolute; inset: -14px; border-radius: 50%; + box-shadow: 0 0 0 3px var(--ring-color), + 0 0 28px 4px color-mix(in srgb, var(--ring-color) 35%, transparent); + transition: box-shadow 320ms ease; +} +.orb[data-state="learner"] { --ring-color: var(--accent); animation: breathe 1.4s ease-in-out infinite; } +.orb[data-state="client"] { --ring-color: var(--clay); /* JS가 RMS 음량을 --amp로 주입 */ } +@keyframes breathe { 0%,100%{ transform: scale(1) } 50%{ transform: scale(1.035) } } +``` + +음량 연동은 JS `onVolumeChange`/RMS → CSS 변수 `--amp`로 box-shadow blur 미세 모듈레이션. **막대 파형(equalizer) 금지** — 너무 "녹음 앱"스럽고 자극적. 호흡하는 빛이 임상적이다. + +### 5.3 실시간 자막 (Transcript) — 대본 스타일 + +음성이 주, 자막은 보조. stage 하단, **아래→위 흐름**. 채팅 버블 양산형 회피, 대본(script) 스타일: + +``` +───────────────────────────────────── + 내담자 14:32 + 요즘은 그냥… 아침에 일어나는 게 + 제일 힘들어요. + 나(학습자) 14:33 + 아침에 일어나는 것조차 버겁게 + 느껴지시는군요. 그 마음을 조금 + 더 들려주실 수 있을까요? +───────────────────────────────────── +``` + +- 화자 라벨 12px 600 메타톤. 내담자 발화는 `--clay-tint` 옅은 배경 틴트 블록, 학습자 발화는 `--accent-tint` 틴트. **버블 외곽선·꼬리·border-left 없음.** 틴트 블록만으로 화자 구분. +- 진행 중 발화(partial)는 `--ink-3` 흐릿하게 타이핑되다 확정되면 `--ink` 또렷하게. +- 자동 스크롤, 위로 스크롤하면 자동스크롤 해제 + "최신으로" 작은 칩. + +### 5.4 회기 단계 표시 (라포 → 탐색 → 개입 → 정리) + +LEFT 패널 세로 트랙커 + 상단 바 가로 미니. **현재 단계만 또렷, 나머지는 은은히** (단계 압박 방지). + +``` +회기 진행 ← kicker + ● 라포 형성 09:12 ← 완료: 채워진 sage 도트 + 소요시간 + │ + ◉ 탐색 진행중 ← 현재: 링+채움, accent, 굵게 + │ + ○ 개입 ← 예정: 빈 도트, ink-3 + │ + ○ 정리 +``` + +- 연결선 1px `--hair`, 완료 구간만 `--accent-bright`. +- **자동 추적 기본, 수동 보정 가능.** 백그라운드 평가 AI가 대화로 현재 단계 추정해 `◉` 이동. 학습자가 직접 "다음 단계로" 가능(자각 촉진). 추정과 인식 어긋나면 회기말 리뷰에서 짚음. +- **자극 배제:** 단계 전환 시 화면 전환·효과음·축하 애니 없음. 도트가 조용히 채워질 뿐(400ms ease). + +### 5.5 실시간 피드백 — "거슬리지 않게" (이 설계의 심장) + +**3단계 강도 모델**을 학습자가 토글로 고른다 (하단 컨트롤 바 segmented control, 기본=은은한 신호): + +| 모드 | 실시간 표시 | 대상 | +|---|---|---| +| **몰입(Immersive)** | 라이브 신호 전혀 없음. 백그라운드 기록만. 모든 피드백 회기말. | 숙련·실전 압박 연습 | +| **은은한 신호(Ambient)** ★기본 | 우측 패널에 **앰비언트 도트 1개만**, 색으로 톤만. 텍스트 없음(호버 시) | 일반 연습 | +| **코칭(Coached)** | 발화 직후 짧은 텍스트 힌트 페이드인 | 초보·특정 기법 집중 | + +#### 앰비언트 신호 (기본 모드 핵심) + +우측 패널에 **단 하나의 부드러운 도트**가 직전 발화 평가 톤만 색으로 전한다. 점수·숫자·순위 **없음.** + +``` +라이브 신호 ← kicker + ● 공감적 반영 ← pos 도트 + 한 단어. 6초 후 페이드. + 최근 흐름 · · ● · ◦ · ● ← 최근 6개 발화 톤 미니 시퀀스(클릭 가능) +``` + +- 도트 색: `--pos-solid`(잘함) / `--warn-solid`(살펴볼 점) / `--ink-3`(중립). **빨강·점수 없음.** +- 등장: 발화 종료+평가 완료 시 opacity 0→1, 350ms. **6초 후 자동 페이드** — 누적되면 대시보드가 됨. +- 거슬림 최소화 3원칙: ①한 번에 1개 ②텍스트 기본 숨김(색만) ③시간 제한 페이드. 진동/소리/번쩍임 전무. 주변시로 "느낄" 수준. + +#### 코칭 모드 텍스트 힌트 + +``` +코칭 힌트 ← kicker + 방금 질문은 '예/아니오'로 닫혀 있었어요. + 내담자가 더 말하도록 열어보면 어떨까요? + 예: "그때 어떤 마음이 드셨는지 들려주세요." + [ 알겠어요 ] ← 가볍게 dismiss +``` +- `--accent-tint` 배경 틴트 블록(border 없음), 13~14px. 예시 문장 `--ink-2` 이탤릭. 한 번에 1개, 새 힌트 오면 이전 것 교체(stack 안 함). +- **위험 신호만 예외적 즉시 표시(모드 무관):** 자해·위기 언급을 학습자가 놓치면 모드 무관하게 우측에 차분한(빨강 아닌 `--warn-solid`) 알림 — "안전 점검: 방금 위기 신호가 있었어요." 교육적으로 놓치면 안 되는 지점이라 항상 노출. + +#### 내담자 상태 미터 (우측 상단) — "반응 읽기 연습"의 거울 + +``` +내담자 상태 +감정 톤 불안 ▓▓▓▓░░ ← 가로 게이지, clay 계열, 라벨 텍스트 +마음 열림 ▓▓░░░░ ← 라포 진척 대리지표 +``` +게이지는 매우 천천히(2s ease) — 실시간 흔들림이 산만하지 않게. 막대 라운드 없는 사각, 4px 높이. + +### 5.6 하단 컨트롤 바 + +- **마이크 버튼(주 컨트롤):** 56px 원형. 학습자 말하는 중 = `--accent` 채움 + 호흡. 음소거 = 아웃라인. push-to-talk 또는 토글. 상태 텍스트 한 줄("듣고 있어요" / "마이크 꺼짐"). +- **일시정지:** 타이머·평가·녹음 모두 정지. 정지 중 stage 차분한 오버레이. +- **종료:** 오발동 방지 — **밀어서 종료(slide-to-confirm)** 또는 0.8s 길게. 종료가 유일하게 진한 경고색(`--crit-solid`) 허용 영역. +- 접근성: 키보드(Space=마이크, P=일시정지), `--focus-ring` 2px. 음성 상태는 색만이 아니라 텍스트 병기(색각 이상 대응). + +### 5.7 회기 종료 후 리뷰 화면 (진짜 학습이 일어나는 곳) + +실시간에선 일부러 참았던 정밀 평가를 여기서 푼다. 구조: **타임라인 + 기법 라벨 + 슈퍼바이저 코멘트 + 개선점.** + +``` +┌──────────────────────────────────────────────────────────────────────────┐ +│ 리뷰 · 시나리오:우울 호소 20대/회기1 오디오 다시듣기▷ PDF 내보내기 │ +├──────────────────────────────────────────────────────────────────────────┤ +│ 한 줄 요약 (큰 문장 하나, 24px/600 — 에디토리얼 미니멀의 핵심) │ +│ "라포는 안정적으로 형성됐지만, 탐색 단계에서 닫힌 질문이 반복돼 │ +│ 내담자의 자기개방이 일찍 멈췄습니다." │ +├───────────────────────────────────────┬──────────────────────────────────┤ +│ LEFT (62%) 타임라인 + 자막 │ RIGHT (38%) 분석 사이드 │ +│ 회기 타임라인(가로 막대) │ ── 기법 사용 분포 ── │ +│ 라포▏▏탐색▏▏▏▏▏개입▏▏정리▏ │ 반영적 경청 ▓▓▓▓▓▓ │ +│ ▲ 마커(코멘트·기법·하이라이트) │ 개방형 질문 ▓▓ │ +│ ─── 스크롤 자막(마커 연동) ─── │ 닫힌 질문 ⚠ ▓▓▓▓▓ (살펴볼 점) │ +│ 나 14:33 ◆[감정 반영] … │ ── 잘한 순간 ── │ +│ └ 슈퍼바이저: "좋은 반영…" │ ── 다음 회기 개선점(최대 3개) ── │ +└───────────────────────────────────────┴──────────────────────────────────┘ +``` + +- **타임라인(가로):** 4단계 너비 비례 구간으로 색 농담 구분. 마커 = 기법 라벨·살펴볼 순간·잘한 순간·슈퍼바이저 코멘트(이모지 아님, SVG 글리프/도트). 클릭하면 좌측 자막 점프 + 오디오 그 시점으로. +- **자막 + 인라인 분석:** 학습자 발화마다 기법 라벨 칩(`[반영적 경청]` `[개방형 질문]` `[닫힌 질문]`), `--accent-tint`/`--warn-tint` 배경, border 없음 11px. 라벨 아래 들여쓰기로 슈퍼바이저 코멘트(AI 자동 + 교수자 직접). AI코멘트 `--ink-2`, 사람 코멘트 `--accent` + "교수자" 태그. +- **분석 사이드:** 기법 사용 분포(가로 게이지, 점수 아님 빈도+적절성, 과다·과소만 `--warn` 라벨). 잘한 것 먼저, 개선점 **최대 3개**(실행 가능 구체적, 각 항목 타임라인 마커 연결). 다음 회기 개선점은 "다음에 시도할 한 문장" 형태. +- **톤:** 점수·등급·합불 **없음.** 상담 역량은 성장 곡선이지 시험이 아니다. 추세는 별도 "학습자 성장" 화면에서만, 단일 리뷰엔 절대점수 안 보임. PDF 내보내기(슈퍼바이저 제출), 오디오 다시듣기, 클립 공유. + +--- + +## 6. 3역할 대시보드 (학습자 / 교수자 / 관리자) + +### 6.1 4가지 설계 결정 (근거) + +| 결정 | 근거 | +|---|---| +| **레이더차트 금지, small-multiples 라인 채택** | 레이더는 항목 순서로 모양 왜곡 + 범주선 "증가/감소" 착시. Tufte small-multiples + 공통 축이 정확. 윤찬 "카드덤프 금지"와도 정합. | +| **신뢰·차분 = 단일 accent + 넉넉한 여백 + 위계** | 의료 UI: blue=신뢰, green=정상범위, **red는 진짜 임상 긴급에만 예약**. whitespace·typography로 불안 완화. | +| **교수자 화면은 "인지"가 아니라 "개입"까지** | 교사용 LA 대시보드 대부분이 awareness만 주고 actionable intervention이 없다는 비판 → 첫 화면을 "개입 필요 학생" 액션 큐로. | +| **역할 전환 = 컨텍스트 라벨 + accent 토큰** | 과한 색·뱃지 남용은 양산형. 좌상단 역할 라벨 + accent 한 토큰 교체로 "지금 누구의 화면인지"만 조용히. | + +### 6.2 역할별 accent 오버라이드 + +```css +body[data-role="learner"] { --accent:#3E7A6E; --accent-bright:#5F968B; --accent-tint:#EEF4F2; } /* 세이지-틸: 성장 */ +body[data-role="instructor"] { --accent:#3A5BA0; --accent-bright:#5478C4; --accent-tint:#EAEFF7; } /* 인디고-블루: 감독 */ +body[data-role="admin"] { --accent:#5B5F6B; --accent-bright:#7D818E; --accent-tint:#EEEFF1; } /* 슬레이트: 운영 */ +``` + +세 역할 모두 채도를 낮춰 자극 없게. red(`--crit-solid`)는 어느 역할에서도 장식으로 안 쓴다 — 오직 "지금 개입해야 하는 학생/세션" 단 한 곳. + +### 6.3 공통 앱 셸 (Shell) + +같은 골격 공유, 좌측 네비 항목 + 좌상단 컨텍스트 라벨만 차이. 역할 전환 시 화면 리셋이 아니라 메뉴·accent만 바뀜. + +- 톱바 56px `background:var(--surface); border-bottom:1px solid var(--hair);` (그림자 없음). +- 좌측 네비 기본 72px 아이콘 전용(Lucide stroke 1.75), hover/포커스 시 220px 슬라이드(160ms). 활성 = 아이콘 `color:var(--accent)` + **3px 강조바 금지** → 아이콘 배경 `var(--accent-tint)` `border-radius:8px` 알약형(네비 알약만 8px 관용). +- **역할 컨텍스트 라벨**: 톱바 좌측, `font-family:var(--font-num); font-size:13px; letter-spacing:0.04em; color:var(--accent);` — "학습 대시보드"/"교수 콘솔"/"운영 콘솔". "지금 누구로 보고 있나"의 유일하고 조용한 신호. +- **권한 표현 원칙: 권한 없으면 DOM에서 제거.** disabled 회색처리도 안 함 — 존재를 숨김. 화면이 깨끗해지고 "내가 못 하는 것" 노이즈 제거. 임상 데이터 프라이버시(관리자는 학생 개별 성장곡선·transcript 기본 접근 불가, 메뉴 자체 렌더 안 함). + +### 6.4 학습자 대시보드 (홈 = "당신은 나아지고 있다") + +``` +COUNSELING PRACTICE · 4주차 ← kicker +수련생님, 지금까지 12회 연습했어요. ← h1 28px/700 +최근 라포 형성이 눈에 띄게 안정됐습니다. ← lead, accent .em + +┌─────────────────────────────┐ ┌───────────────────────────┐ ← cols 7:5 비대칭 +│ 종합 상담 역량 │ │ 다음 연습 │ +│ ╭───────╮ │ │ ▸ 불안 호소 내담자·중급 │ +│ │ 72 │ +6 ▲ │ │ 이전 약점: 침묵 다루기 │ +│ ╰───────╯ 지난주 대비 │ │ [ 연습 시작 → ] │ +│ (단일 게이지, 둘레 progress)│ │ 추천 이유: 라포는 올랐지만 │ +└─────────────────────────────┘ │ 위기개입 정확도 정체 │ + └───────────────────────────┘ +역량별 추이 (최근 8회) ← kicker +┌────────┐┌────────┐┌────────┐┌────────┐ ← small multiples 4열, 공통 y축 0-100 +│라포형성 ││기법사용 ││개입정확도││공감반영 │ +│ ╱╲__╱ ││ __╱╲╱ ││ ╲__╱╲ ││ __╱╱ │ +│ 82 ▲ ││ 68 → ││ 61 ▼ ││ 75 ▲ │ +└────────┘└────────┘└────────┘└────────┘ +최근 피드백 모아보기 (테이블, 카드 아님) ← kicker +6/24 불안 내담자 │ "침묵 견디지 못하고 성급히 해결책 제시…" — 개입 ▸ +6/22 분노 내담자 │ "반영 정확. 다만 감정 명명이 늦음." — 공감 ▸ +``` + +- **종합 게이지(radar 대체):** 단일 원형. SVG `` 트랙 `stroke:var(--neutral-150)`, fill arc `stroke:var(--accent); stroke-linecap:round`. 중앙 숫자 48px/700. 변화량 `+6 ▲` = `--pos-solid`, 하락이면 `--warn-solid`(앰버, 빨강 아님). 아래 한 문장 자연어 요약. +- **역량별 추이 = small multiples:** 4개 동일 크기 미니 라인. **공통 y축 0-100**(첫 카드만 0/50/100 옅게 표기). 라인 `stroke:var(--ink-2); stroke-width:1.5`. 마지막 점만 `r:3; fill:var(--accent)`. 카드 `background:var(--surface); border:1px solid var(--hair); border-radius:8px; padding:16px`. **상단 강조선·좌측바 금지.** 우하단 최신값+추세 화살표(▲`--pos`, →`--ink-3`, ▼`--warn`). 호버 툴팁, 클릭 시 세션 상세. +- **피드백 모아보기 = 테이블, 카드 아님:** 날짜/내담자유형/인용 코멘트/태그/`▸`. zebra 없이 행 `border-bottom:1px solid` `--paper-2`. 행 hover `background:var(--accent-tint)`. 카드 격자로 흩뿌리지 않고 시간순 한 컬럼 위계. + +### 6.5 교수자 콘솔 (홈 = "누구를 먼저 도와야 하는가") + +핵심: **awareness가 아니라 triage.** 첫 화면 최상단이 "개입 필요 학생" 액션 큐. + +``` +상담실습 II · 28명 담당 ← kicker +이번 주, 3명에게 개입이 필요합니다. ← h1, accent .em + +┌──────────────────────────────────────────────────────────────┐ ← 개입 큐(최우선, 풀폭) +│ ● 박서연 개입정확도 3주 연속 하락(61→52→44) 미연습 5일 ▸ │ 좌측 dot = --crit-solid +│ ● 이도현 위기상담 시뮬 중도이탈 2회 마지막 6/20 ▸ │ +│ ● 최민준 라포 점수 급락, 피드백 미열람 7건 ▸ │ +└──────────────────────────────────────────────────────────────┘ + +학생별 현황 [정렬: 위험도▾] [영역: 전체▾] +학생 세션 라포 기법 개입 공감 추세 마지막 상태 +김수련 12 82↗ 68→ 61↘ 75↗ ▁▃▅▆▇ 6/24 양호 +박서연 8 55↘ 49↘ 44↘ 58↘ ▇▅▃▂▁ 6/19 ● 개입 +이도현 14 88↗ 79↗ 71→ 84↗ ▅▆▆▇▇ 6/23 양호 +``` + +- **개입 큐(triage):** 풀폭 패널. 각 행 좌측 dot `8px; background:var(--crit-solid)`(red의 유일한 정당한 사용처). 사유 = 수치 변화 + 행동 신호(미연습일·미열람·중도이탈)를 자연어로. 알고리즘이 뽑되 교수가 최종 판단하도록 "왜"를 노출. +- **반 평균 역량 분포 = 가로 막대**(레이더 아님): fill `var(--accent)`, 트랙 `var(--paper-2)`. **최저 항목만** 우측 `← 취약` `color:var(--warn-solid)`. 나머지 차분한 무채. +- **학생별 테이블:** sparkline(▁▃▅▆▇, 셀 안 7px SVG, 최근 8회 종합점수). 역량 셀 `82↗`(화살표 색만 시맨틱). 정렬 기본 위험도 순. 상태 "양호"는 `--ink-3` 무채, "● 개입"만 `--crit-solid` dot. red를 한 곳에 집중. 행 클릭 → 학생 드릴다운(학습자 화면 + 세션별 utterance-level 평가 타임라인). + +### 6.6 관리자 콘솔 (홈 = "시스템이 건강한가 · 누가 무엇을 했는가") + +핵심: **운영 신뢰성 + 감사.** 학습 내용엔 개입 안 하고 시스템·사용·교수활동 감사에 집중. 무채 슬레이트로 가장 차분. + +- **운영 KPI 5:** 사용자/강좌/세션주/가동%/API오류. mono 32px. 오류 0 초과 시 해당 KPI만 `--warn-solid`, 임계 초과 `--crit-solid`. 정상 땐 전부 무채 — "조용한 게 정상". +- **세션 추이 = 단일 라인차트**(막대 아님). 피크 1점만 라벨. +- **음성·AI 사용 + 비용:** OpenAI 음성 분/평가 토큰/추정 비용/예산 대비 게이지(80% 넘으면 `--warn-solid`). 운영비 가시화(윤찬 graphify 비용 통제 문화). +- **교수 활동 감사 테이블:** 교수별 피드백 열람률(품질 신호)·시나리오 발행·마지막 활동. 저조 지표만 `← 저조` `--warn`. "⚠ 점검" = 관리자 리마인드 대상. +- **Audit log:** 시간순 한 컬럼. 행위자·행위·대상. 색 없음(감사는 중립). 클릭 시 상세. + +### 6.7 데이터 시각화 하드룰 (전 역할 공통) + +| 상황 | 채택 | 금지 | +|---|---|---| +| 한 사람 여러 역량 **추이** | small multiples 라인(공통 y축) | 레이더/스파이더 | +| 여러 역량 **현재값 비교** | 가로 막대(공통 0-100), 최저값만 강조 | 레이더, 전항목 색칠 | +| 한 지표 **시간 추이** | 단일 라인(피크/최신 1점만 라벨) | 3D·면적 그라데이션 | +| 테이블 안 **추세 압축** | sparkline(7px) + 화살표 | 셀마다 미니 도넛 | +| **종합 한 숫자** | 단일 원형 게이지 + 변화량 | 여러 도넛 격자 | + +**차트 공통 스타일:** 격자선 최소(y축 0/50/100 옅은 점선만, 세로 격자 없음). 라인 `stroke-width:1.5`, 점은 최신/이상치만. 색은 데이터 의미에만(추세 ↗`--pos`/↘`--warn`/→무채, 평상시 라인 `--ink-2` 무채 → 색 인플레이션 방지). 진입 라인 draw 280ms, 게이지 arc 400ms ease-out 1회만. 깜빡임·펄스·glow 없음. 모든 수치 `tabular-nums`. + +--- + +## 7. 컴포넌트 원칙 (요약 카탈로그) + +공통: **border-radius 절제(6~12px, 알약 금지), 그림자 절제(헤어라인 우선), border는 `--hair`.** 카드는 쓰되 균등 4-4-4 격자 남용 금지 → 비대칭·표·도식. + +### 7.1 버튼 (위계 3단, 알약형 금지 radius 8px) + +| 종류 | 배경 | 텍스트 | 보더 | 용도 | +|---|---|---|---|---| +| Primary | `--accent` | `--text-on-accent` | none | 세션 시작·저장 (화면당 1~2개) | +| Secondary | `--bg-surface` | `--accent` | 1px `--accent` | 보조 액션 | +| Ghost | transparent | `--text-body` | none | 취소·텍스트 액션 | +| Danger | `--crit-solid` | #fff | none | 세션 강제 종료 등 위급만 | + +```css +.btn { font: 600 var(--fs-sm)/1 var(--font-sans); padding: 10px 18px; + border-radius: var(--radius); border:1px solid transparent; + transition: background .16s ease, transform .12s ease; cursor:pointer; } +.btn-primary { background: var(--accent); color: var(--text-on-accent); } +.btn-primary:hover { background: var(--accent-deep); } +.btn:active { transform: translateY(.5px); } /* 미세 누름, 과한 모션 X */ +.btn:focus-visible { outline:none; box-shadow: 0 0 0 3px var(--focus-ring); } +``` + +### 7.2 입력 (radius 6px, 포커스 시 보더색 + 얇은 ring, 좌측바 X) + +```css +.input { width:100%; background: var(--bg-surface-2); color: var(--text-body); + border:1px solid var(--border-strong); border-radius: var(--radius-sm); + padding:10px 12px; font:var(--fs-body)/1.5 var(--font-sans); + transition:border-color .15s, box-shadow .15s; } +.input:focus { border-color: var(--border-focus); outline:none; box-shadow: 0 0 0 3px var(--focus-ring); } +.input[aria-invalid="true"] { border-color: var(--crit-solid); } +``` + +### 7.3 카드 / 패널 (헤어라인 + 미세 그림자, 상단 강조선 금지) + +```css +.card { background: var(--bg-surface); border:1px solid var(--border-subtle); + border-radius: var(--radius); padding: var(--sp-5); box-shadow: var(--shadow-sm); } +.panel { background: var(--bg-surface); border:1px solid var(--border-subtle); + border-radius: var(--radius-lg); padding: var(--sp-6); } +.panel-tint { background: var(--bg-tint); border-color: transparent; } /* 강조 패널: 틴트 배경 */ +``` + +### 7.4 평가 피드백 콜아웃 (이 플랫폼 핵심, 좌측 컬러바 금지 → 배경 틴트 + 도트 + kicker) + +```css +.callout { border-radius: var(--radius); padding: var(--sp-4) var(--sp-5); + font-size: var(--fs-sm); line-height:1.6; } +.callout .clh { font: 600 var(--fs-xs)/1.4; letter-spacing:.04em; + display:flex; align-items:center; gap:8px; margin-bottom:6px; } +.callout .clh::before { content:""; width:7px; height:7px; border-radius:50%; } /* dot, 바 아님 */ +.callout.pos { background:var(--pos-tint); color:var(--pos-text); } +.callout.pos .clh::before { background:var(--pos-solid); } +.callout.info { background:var(--info-tint); color:var(--info-text); } +.callout.info .clh::before { background:var(--info-solid); } +.callout.warn { background:var(--warn-tint); color:var(--warn-text); } +.callout.warn .clh::before { background:var(--warn-solid); } +.callout.crit { background:var(--crit-tint); color:var(--crit-text); } /* 가드레일 위반만 */ +.callout.crit .clh::before { background:var(--crit-solid); } +``` + +피드백 강도 매핑: **잘함→`pos`(녹색), 일반 코칭/관찰→`info`(슬레이트), 페이싱·라포 주의→`warn`(앰버), 가드레일/위급→`crit`(적색).** 학습자 화면 기본 피드백은 info/pos가 다수가 되도록. + +### 7.5 라이브 음성 오브 (§5.2 참조) + +내담자 발화=sage, 학습자 발화=clay로 화자 구분(채도 낮게). 깜빡이는 빨강 금지 → 호흡 펄스. + +--- + +## 8. React 구현 노트 + +### 8.1 컴포넌트 트리 + +``` + // body[data-role] 설정, 네비·톱바 + // 권한 없는 항목 필터링 후 렌더(DOM 제거) + // mono accent 라벨 + + ├ Hero1줄 + cols(Gauge, NextPractice) + MiniMultiples×4 + FeedbackTable + ├ InterventionQueue + KPIs + ClassDistribution + StudentTable(sparkline) + └ OpsKPIs + SessionTrend + UsageCost + FacultyAudit + AuditLog + + + ├ + ├ // CSS grid 320/1fr/300, RN은 Flex + │ ├ // LEFT + │ ├ // CENTER + │ │ ├ // §4 아바타 + │ │ ├ + │ │ └ + │ └ // RIGHT + │ ├ + │ ├ // 은은한 신호 + │ ├ // 코칭 모드만 + │ └ + └ + + + ├ + ├ // 큰 한 문장 + ├ + ├ + └ +``` + +### 8.2 상태 관리 + +- 음성 이벤트(`onModeChange`/`onVolumeChange`/partial transcript)는 OpenAI Realtime 스트림 → 단일 `SessionStore`(Zustand 권장)로 모아 `VoiceOrb`·`LiveTranscript`·`FeedbackRail`·`ClientAvatar` 구독. +- 평가 AI는 **별도 채널(백그라운드)**로 발화 단위 신호를 throttle해서 push. `FeedbackRail`이 6초 페이드 큐로 소비. 평가와 음성 상태 표시는 분리(평가가 음성 UX를 막지 않게). +- 차트 프리미티브 4종 먼저 구축: `` `` `` `` (§6.7 룰 내장, props=data+role토큰). 학습자 컴포넌트가 교수 드릴다운에서 재사용됨. + +### 8.3 구현 순서 + +1. `tokens.css`(§3) + 앱 셸(§6.3) — 셸은 3역할 공통이므로 1회만. +2. 차트 프리미티브 4종. +3. 학습자 홈 → 교수 홈 → 관리자 홈 순(재사용 극대화). +4. 세션 화면: 폴백 추상 오브 먼저(음성 파이프라인·4-state 배선 검증) → 반구상 SVG 아바타 → 정서 6파라미터 → 페르소나 JSON. +5. 리뷰 화면 + utterance-level 평가 타임라인. +6. 역할 가드: 라우터에서 `role` 기반 메뉴·라우트 **렌더 제외**(disable 아님). +7. (여유) Rive 아바타 고도화. 안 되면 SVG 1차안으로 출시(폴백 안전). + +### 8.4 RN 전환 대비 + +레이아웃 토큰은 절대 px 의존 최소화(rem·flex 우선). SVG→`react-native-svg`, Web Audio→`react-native-audio-api`, Rive→RN 런타임. 브레이크포인트 sm:640/md:768/lg:1024/xl:1280. + +--- + +## 9. 안티패턴 체크리스트 (HARD NO — 출시 전 자가검증) + +1. **border-left 강조선 / 좌측 사이드바 형태 강조줄** — 피드백·콜아웃·카드·활성표시에 색 막대 금지. 위계는 kicker+weight+여백+dot+배경틴트. +2. **이모지** — UI·일러스트·SVG 내부 장식까지 전부 금지. 아이콘은 Lucide(stroke 2, 직각). 평가 결과에 체크/엑스 이모지 금지. 추세는 ▲▼→ 기하 화살표. +3. **카드 덤프 / 균등 그리드 무한반복(4-4-4)** — 비대칭(7:5) 레이아웃·데이터 테이블·도식으로. 학습자 홈은 게이지+small-multiples+테이블, 교수 홈은 triage 큐+KPI+테이블. +4. **양산형 SaaS 랜딩** — "그라데이션 히어로 + 떠다니는 mockup + 3컬럼 feature 카드 + 보라 그라데이션 CTA" 공식 금지. +5. **과한 그라데이션·glow·gradient-text·큰 blur** — 깊이는 1px 헤어라인. +6. **둥근 모서리 과다(16px+)·말랑 일러스트** — 거의 직각(6~12px). 원형은 오브·아바타·dot만. +7. **게임화 폭격** — 점수 팡파레, 빨간 X, 레벨업 배지, 별점 5개 즉시. 평가는 서술형·비모달·회고 시점. +8. **순흑(#000)·순백(#FFF)** — `--ink:#1C2A2A` / `--paper:#FBFAF8`. +9. **고채도 메디컬 블루 단독 의존** — 세이지-틸로 차별화 + 온기. +10. **실시간 피드백 모달** — 상담 흐름 중 팝업 금지. 차분한 사이드 패널로. +11. **레이더/스파이더 차트** — small-multiples 라인 + 단일 게이지로. +12. **권한 없는 메뉴 disabled 회색 나열** — DOM에서 제거. +13. **빨강 장식 사용** — `--crit-solid`는 (교수)개입 큐·"개입" 상태·(관리자)임계 오류에만. +14. **One screen, one message 위반** — 각 화면 최상단 h1이 그 화면 단일 메시지("나아지고 있다"/"3명 개입 필요"/"정상 운영"/"듣고 있어요"). + +--- + +## 10. 핵심 의사결정 5줄 (윤찬이 기억할 것) + +1. **컬러: 세이지-틸 `#3E7A6E` 단일 accent + 테라코타 `#B0735C` 온기 보조.** 의료 블루 회피, 저채도로 임상+따뜻함 동시. 역할별 accent 한 토큰만 교체. +2. **아바타: 사실적 얼굴(오인·불쾌한 골짜기·편향)도 추상 오브(비언어 연습 불가)도 답 아님 → 사실성 35~45% 양식화 반구상 흉상 + 호흡 광배.** 눈·눈썹·입·어깨 4채널 × 정서 6파라미터. SVG 1차 → Rive 2차. 미성년은 realism 더 낮추고 `교육용 가상 내담자` 라벨 상시. +3. **세션 화면: 실시간 피드백은 "신호(색 도트, 6초 페이드, 1개)"까지만, 정밀 평가는 회기말 리뷰로.** 학습자가 모드(몰입/은은/코칭)를 고른다. 음성 상태는 막대 파형이 아니라 빛의 호흡(sage=학습자, clay=내담자). +4. **시각적 무게 = 주의 배분.** 내담자 stage 60%, 평가 패널 15%·평시 접힘. 화면은 대시보드가 아니라 상담실. +5. **대시보드: 레이더 금지 → small-multiples 라인 + 단일 게이지.** 점수·합불 없음(성장 추세만). 교수자 첫 화면은 triage 큐. 권한 없으면 DOM 제거. + +--- + +## 참고 (실조사 출처) + +**디자인 언어·시스템** +- Linear Brand Guidelines / Stripe Design / Calm·Headspace 색 (accent rationing, 저채도, 호흡 모션) +- Fuzzy Math — Color Palettes of Mental Healthcare UI / Villa Healing Center — Calming Colors +- Halo Lab — Digital Healthcare UX Trends 2026 / Progress — Color Psychology in Healthcare +- Pretendard (orioncactus/pretendard) / az-loc — Best Fonts for CJK Websites 2026 +- Aufait UX — Accessible Color Tokens / WCAG Contrast Guide 2026 + +**아바타** +- Behavioral Sciences 2026 — Human-like/Animal-like/Object-like LLM Virtual Doctor Avatar +- arXiv 2506.11890 — Graduated Realism (AI Avatars in VR Teacher Training) +- PMC12692777 — AI Physician Avatars (윤리·편향·오인 위험) +- NYU Steinhardt — AI Avatar for Social Work Counseling Practice +- ElevenLabs UI Orb / orb-ui (4-state) / Rive vs Lottie 2026 / arXiv 2502.16038 (Valence-Arousal-Dominance) + +**세션·대시보드** +- arXiv 2502.18673 — Scaffolding Empathy (utterance-level 피드백 학습효과 vs 인지부하) ← 2단 피드백 모델 근거 +- arXiv 2505.02428 — LLM-Simulated Practice/Feedback for Counselors +- assistant-ui / Vocalis / Agora (음성 상태 시각화) +- Scott Logic·Observable — Radar Charts 비판 (small-multiples 대체 근거) +- GoReact — Counseling Skills Rubric / Springer Open — LA Dashboard actionable intervention 비판 +- Eleken·Fuselab — Healthcare UI/UX 2026 + +**vault 연계 자산** +- `Resources/50_Tech_Dev/디자인/slide-base.css` (무채 골격·강조 위계 계승) +- `Resources/50_Tech_Dev/디자인/[디자인 시스템] HTML 슬라이드·보고서 베이스 (2026-06).md` (한신대/의료 행에 `--accent:#3E7A6E` 채움) diff --git a/docs/MASTERPLAN.md b/docs/MASTERPLAN.md new file mode 100644 index 0000000..6c947c5 --- /dev/null +++ b/docs/MASTERPLAN.md @@ -0,0 +1,324 @@ +# 한신대학교 SW중심대학 산학협력 — 생성형 AI 심리상담 시뮬레이션 플랫폼 마스터플랜 + +> 통합 기준 문서 (SoT, Single Source of Truth) · 작성 2026-06-25 · 트웬티온스 산학협력 +> 제약: **20주 / 매칭금 500만 / 기존 풀스택팀** +> +> **🔄 2026-06-25 업데이트 (윤찬 확인):** **IRB 정식 심의 불필요.** 수련생 대상 *교육 활용*이라 인간 대상 임상시험이 아니므로 IRB 면제. → 아래 "IRB 승인"이 걸려있던 의존성/리드타임은 모두 해소(Phase 3 게이트 제거, 즉시 착수 가능). 단 **미성년 원본 사례데이터(0615 등) 활용동의 + 개인정보 처리방침**은 별도로 한신대 공문 확인 유지(IRB와 무관한 데이터 거버넌스 사안). +> 본 문서는 9개 도메인 전문 설계서를 단일 마스터플랜으로 종합한다. 각 도메인 상세는 §부록 A의 설계서 원본 참조. + +상담 수련생 훈련용 — **가상내담자 시뮬레이션 + AI 실시간 피드백 + 안전 가드레일**. 이 플랫폼은 진단·치료가 아니라 **수련생 연습용**(FDA General Wellness / 식약처 비의료기기 포지셔닝)이라는 점을 전 계층에서 고정한다. + +--- + +## 0. 한 페이지 요약 (의사결정 고정값) + +| 영역 | 확정 결정 | 핵심 근거 | +|---|---|---| +| **AI 엔진** | 기본=Anthropic Messages API(Opus 4.8/Sonnet 4.6) **엔진 어댑터** 뒤에 배치. `claude -p`(Max OAuth)는 옵션 플래그·개발 시연용 | `claude -p`+OAuth 과금 누수(이틀 $1,800 사례)·동시성 1프로세스 한계·rate limit 마비 리스크. 어댑터 한 줄로 전환 가능하게 흡수 | +| **3-AI 구조** | 상담사 AI(선택) / 내담자 AI / 백그라운드 평가 AI. **정보 비대칭을 DB `visible_to[]` 컬럼이 강제**(프롬프트 신뢰 X) | 상담사AI가 내담자 CCD를 보면 시뮬레이션 가치 0 | +| **상태머신** | 라포→탐색→개입→정리 전이는 **FastAPI 백엔드가 결정론적으로 소유**(LLM 아님) | 단계 흐릿하면 수련생 채점 신뢰도 붕괴 | +| **저항 엔진** | 내담자 저항을 프롬프트 형용사 아닌 **상태머신 수치**로 단계 주입 (핵심 차별기술) | CARE-Bench가 LLM positivity bias(과순응) 정량 확인 | +| **피드백** | 피드백 패널 = 1급 시민. **자유연습(피드백 OFF) 기본 차단** | Stanford CARE RCT: 연습만 d=-0.52 하락, 연습+피드백 집단격차 d=0.72 | +| **프론트** | React 19 + SSE, pnpm/Turborepo 모노레포(`packages/core` 100% 공유, 추후 RN) | 웹→네이티브 전환을 구조로 증명, 20주 과대약속 회피 | +| **백엔드** | FastAPI + SSE 스트리밍, 상태머신·가드레일·엔진 어댑터 | | +| **DB** | NAS PostgreSQL 16(+pgvector) 단일 SoR. Timescale은 확장 시 hypertable만 선택 | 20명 규모에 Timescale 과잉 | +| **배포** | Docker Compose 단일 호스트 모놀리식(5~6 컨테이너), Caddy 리버스프록시. K8s 금지 | 20주·인력 제약에 모놀리식이 최적 | +| **외부노출** | chanpaca.net = Cloudflare named tunnel(일반) **+ SSE 경로는 버퍼링 우회 분리**(Tailscale Funnel) | Cloudflare가 SSE를 ~100KB 버퍼링 + 100초 timeout → 50분 상담 스트림 충돌 | +| **음성** | 1차 캐스케이드(Deepgram STT → 로컬 엔진 → OpenAI gpt-4o-mini-tts). s2s 1차 탈락 | transcript가 평가·로깅·재귀학습·가드레일의 1급 자산. s2s는 transcript 손실 | +| **인증** | BFF + OAuth 2.1 Auth Code + PKCE(S256). 토큰은 서버(Redis)에만, 브라우저엔 HttpOnly 쿠키 | 미성년 사례데이터+상담 민감정보 → XSS 토큰탈취 원천 차단 | +| **안전** | MIND-SAFE식 다층 분리 + 한국어 자살콘텐츠 분류(Llama Guard 한국어 미지원 공백 보강) | 재현율=법적 책임 최우선 | + +--- + +## 1. 시스템 아키텍처 개요 + +### 1.1 물리 토폴로지 (인바운드 포트 0) + +``` + [교수님 브라우저] https://chanpaca.net + │ + ┌─────────┴──────────┐ + │ Cloudflare Edge │ ← named tunnel(영구 URL, TLS/DDoS) + │ (정적·일반 API) │ ⚠ SSE는 버퍼링 → 분리 경로 + └─────────┬──────────┘ + │ (outbound-only, 개방포트 0) +┌───────────────────────────┼────────────────────────────────────────┐ +│ 로컬 PC / NAS (한국 내 · 데이터 주권) Docker Compose: counsel-net │ +│ ┌────────────┐ │ +│ │ cloudflared│──┐ ┌──────────┐ ┌──────────────────────────┐ │ +│ │(named tun.)│ │ │ frontend │ │ backend (FastAPI) │ │ +│ └────────────┘ │ │ React19 │ │ ┌────────────────────┐ │ │ +│ ┌────────────┐ ├──►│ 정적빌드 │◄──►│ │ 상태머신(결정론) │ │ │ +│ │ Caddy │◄─┘ └──────────┘ │ │ 가드레일/안전레일 │ │ │ +│ │ SSE flush-1 │◄──── [SSE 직결경로] │ │ SSE 스트리머 │ │ │ +│ └─────┬──────┘ (Tailscale Funnel) │ │ 엔진 어댑터(라우터)│ │ │ +│ │ │ └────────┬───────────┘ │ │ +│ ▼ └──────┬────┼──────────────┘ │ +│ stream.chanpaca.net (SSE 전용) │ │ │ +│ ┌─────▼─┐ ┌▼─────────┐ │ +│ ┌────────────────┐ ┌──────────────┐ │engine │ │ voice-gw │ │ +│ │ Postgres 16 │ │ pgvector(RAG)│ │adapter│ │ OpenAI │ │ +│ │ (NAS 영구볼륨) │ │ 임베딩 1024d │ │ ①claude_api(기본) │ │ +│ │ 학습로그·관계형 │ └──────────────┘ │ ②claude_cli(옵션) │ │ +│ │ RLS 이중강제 │ │ ③openai/solar │ │ +│ └────────────────┘ [Presidio PII 마스킹] ──► 외부 LLM(마스킹 후만) │ +└──────────────────────────────────────────────────────────────────────┘ +``` + +**원칙**: ① 인바운드 포트 0 (cloudflared outbound 터널) ② 데이터는 로컬(NAS)을 떠나지 않음, PII 마스킹 후에만 외부 LLM 호출 ③ 전송 계층 추상화(`VITE_SSE_BASE_URL`)로 SSE 경로 무중단 전환. + +### 1.2 논리 레이어 (프롬프트 캐싱 친화) + +``` +[L0 역할+안전가드레일+도식노출금지] ┐ +[L1 페르소나 카드(정적)] ├─ cache_control (입력비 90%↓, 실측 59~70% 절감) +[L2 RAG 임상청크] ┘ +[L3 상태머신 주입(stage, openness)] +[L4 메모리 버퍼(사실 hard-pin)] +[L5 수련생 발화] +``` + +--- + +## 2. AI 3종 상호작용 모델 + +### 2.1 정보 비대칭 (DB가 강제) + +| AI | system 프롬프트 | 볼 수 있는 것 | 절대 못 보는 것 | +|---|---|---|---| +| **① 상담사 AI** (선택/보조) | 마이크로스킬 시연용 | 대화 표면 | 내담자 CCD·DSM 차원·평가결과 | +| **② 가상내담자 AI** | 페르소나 카드+CCD+저항 | 자기 페르소나·대화 | 평가 점수·정답 라벨 | +| **③ 백그라운드 평가 AI** | 루브릭+골든 taxonomy | 전체 회기·CCD·정답 | (전부 봄, 학습자에 노출 X) | + +3-AI는 서로 다른 system 프롬프트의 **독립 `claude -p`/API 호출**. "누가 무엇을 보는가"는 **DB `visible_to[]` 컬럼이 강제**하며 프롬프트를 신뢰하지 않는다. + +### 2.2 턴 사이클 (결정론 상태머신 중심) + +``` +수련생 발화 U_t + → [입력 가드레일] PII 마스킹 + 위기 분류(수련생 실제위기 vs 페르소나 연기 구분) + → [상태머신] stage·effective_openness(t) 계산 + effective_openness = clamp(stage.openness + + rapport_credit(t)*unlock_rate + - resistance*decay(t), 0, 1) + → [내담자 AI] 페르소나 응답 생성 (Structured Outputs, CCD 직접노출 금지) + → [출력 가드레일] 자살수단 정보 차단, ideation_stage ≤ 3 상한 + → [평가 AI 2-tier] + · fast-loop(경량): U_t만 보고 4차원 태깅 (실시간 신호) + · deep-loop(Opus): 단계 전환·회기말, 0-5 채점 + 대안발화 (대시보드) + → [로깅] TurnLog → Postgres (= 재귀학습 JSONL 원천) +``` + +**rapport_credit**: 수련생이 공감·반영·타당화·홀딩을 쓰면 +, 조언점프·평가적언어·유도질문이면 0/−. → **좋은 상담을 하면 내담자가 열리고, 나쁜 상담을 하면 닫힌다.** 0615 실데이터의 "방어가 서서히 풀어지고 있음" 궤적 재현. + +### 2.3 평가 AI 2-tier 루프 (핵심 레버) + +- **fast-loop**: Haiku/Solar 경량, 발화별 4차원(공감·개방질문·검증·이론부합) 태깅 → 콜드스타트 지연·비용 통제 +- **deep-loop**: Opus 4.8, 전체 회기 + 골든라벨 RAG로 0-5 채점(CTRS는 0-6 분리) + 대안발화 제시. **실시간 팝업보다 종료 후 대시보드가 SUS↑·인지부하↓** +- **LLM-judge 보정**: raw 직접사용 금지, 길이정규화, 이중코딩 vs LLM weighted-κ≥0.6 / ICC≥0.75 게이트 통과분만 골든셋 승격 + +### 2.4 가상내담자 페르소나 (DSM-5 = 내부 파라미터, 비노출) + +- **차원적 프로파일**(RDoC 정신): negative_affect·hopelessness·suicide_risk{ideation_stage 1~5}·sleep_disturbance를 0~1 연속값 (단일 DSM 라벨 X) +- **CCD 8요소**(Patient-Ψ): core_belief(worthless 등)·automatic_thought·coping_strategy — **직접 발화 금지**, 행동으로만 드러내 상담자가 추론하게 +- **시드 페르소나 3종(교수 검수 필수 게이트)**: P1=0615 청소년 우울·자퇴·자살사고(hard) / P2=성인 범불안·신체화(moderate, 자살0) / P3=인간중심 미혼모(PCT 훈련). 2차 백로그: ADHD품행·경계선특질·적응장애·위기4~5단계 전용 + +--- + +## 3. 데이터 모델 + Postgres 스키마 요약 + +### 3.1 원천 — 0615 파란색 라벨의 3종 혼재 (실측 확인) + +`축어록_파란색라벨_0615.hwpx`를 직접 파싱(파랑 #3057B9, 라벨 89개·77종) → 한 셀에 **3종이 혼재**함을 확인. 학습 신호 오염 방지를 위해 **3개 분리 필드로 격상**이 스키마 설계의 출발점: +- **(A) 기법 태그**(복수): 공감/반영/탐색/홀딩/타당화/직면/해석/자기개방/안정화/희망고취/위험사정… +- **(B) 내담자 상태 태그**(복수): 비자발/방어/자살사고인정/부정적자기인식/갈등상태… +- **(C) 슈퍼바이저 논평** 2종: `rationale`(근거설명) vs `critique`(과도한 자기개방·비언어반영 부족 등) + +### 3.2 스키마 요약 (PostgreSQL 16, 3 스키마: app / audit / ds) + +```sql +-- ── app: 운영 ────────────────────────────────────────────── +sessions(id, learner_id, persona_id, theory_mode, started_at, ended_at, stage_path JSONB) +turns/utterances( -- 발화 = 최소 원자단위, 모든 라벨/평가/PII가 FK로 매달림 + id, session_id FK, seq, speaker('counselor'|'client'), + stage('라포'|'탐색'|'개입'|'정리'), + text, -- 저장 전 Presidio PII 마스킹 + text_masked, -- 외부 LLM 전송용 + actor_kind, -- 3 AI + 인간(수련생/슈퍼바이저) 통합 출처 + llm_provider, model, tokens_in, tokens_out, cost_usd, visible_to TEXT[]) +-- 라벨은 버전드 코드테이블 + 다대다(복수 라벨) +technique_label_def / client_state_def / stage_def / scale_def(0-5, CTRS 0-6) +turn_technique(turn_id, label_id) / turn_client_state(turn_id, label_id) +feedback_scores(turn_id, dimension, score, rationale) -- 0-5, D3 +supervisor_comment(turn_id, kind('rationale'|'critique'), + text, intent_deviation JSONB{expected, actual, severity, dimension}) -- 윤찬 "의도와 다른 부분" 1급 시민 +safety_events(session_id, trigger_type, ko_risk_level, escalated, latency_ms, created_at) +rag_chunks(id, source, embedding vector(1024), text) + +-- ── audit: append-only 법적 증거 ────────────────────────── +event_log / llm_call_log(cost·드리프트·inference_geo) / audit_log(열람추적) + +-- ── ds: 재귀 학습 파이프라인 ────────────────────────────── +dataset / dataset_item / annotation_round / annotation / export_manifest +-- AI자동(1R) → 인간검수(2R) → IAA 게이트(κ≥0.6, ICC≥0.75) → 골든셋 → JSONL +``` + +### 3.3 거버넌스 (인프라 직결) +- **저장 전 PII 마스킹 하드 게이트**: backend→외부 LLM 경로에 Presidio(MedicalNERRecognizer). 마스킹 후 텍스트만 적재·전송 +- **RBAC + Postgres RLS 이중강제**: 학습자=본인 / 교수자=담당 코호트 / 관리자=전부+교수활동(열람을 audit_log에 기록) +- **데이터 국내처리**: DB=NAS(국내), 민감구간은 국내 모델(Solar/HyperCLOVA) 라우팅 또는 `inference_geo:us` 명시 +- **미성년 원본 4단계 격리**: 원본 축어록은 플랫폼 DB 절대 미적재, 사람이 가명처리·전문가 검수한 CCD 스펙만 진입 (Character.AI 판례 대응) + +--- + +## 3.6 RAG 아키텍처 (2026-06 최신 기준 · 윤찬 요청 보강) + +> RAG는 부가기능이 아니라 본 시스템 3개 축(페르소나 일관성 / 피드백 근거 / 지식)을 떠받치는 핵심. 2026.6 production RAG 합의 = **failure mode 기준으로 사다리 최소한만 오른다**(GraphRAG·Agentic 풀스택 처음부터 X). + +### 용도별 매핑 (서로 다른 RAG) +1. **페르소나 메모리** (내담자 일관성) — 순수 RAG 아님 → **하이브리드 메모리**: 구조화 상태(상태머신 수치) + rolling summary + episodic 벡터 recall. 임상 비네트는 소규모 고정셋이라 검색난도 낮음. +2. **피드백 근거** (0615 발화-라벨 정답셋 + 슈퍼바이저 논평) — 오답비용 큼 → 하이브리드 + 리랭킹 (+확장 시 CRAG 그레이딩). +3. **지식** (DSM-5 진단기준 · 상담이론 인간중심/CBT) — 정적 KB → 하이브리드 + Contextual Retrieval. + +### 확정 스택 (MVP = Phase 1) +- **임베딩 = BGE-M3**: 다국어(한국어 강함) + dense·sparse·ColBERT 멀티벡터를 **단일 모델**로 제공 → 하이브리드와 late-interaction을 한 모델로 흡수. *우리 rag-memory가 이미 BGE-M3 사용 중 → 재활용.* +- **저장/검색 = pgvector(HNSW)**: 기존 NAS Postgres에 인프라 추가 0, 수백만 벡터 sub-100ms. +- **하이브리드 검색**: dense(pgvector) + sparse BM25(PG full-text 또는 BGE-M3 sparse), top-50 회수. +- **Contextual Retrieval(Anthropic)**: 청크 임베딩 전 1문장 맥락 주입 → 검색실패 **49%↓**(+리랭킹 시 **67%↓**). +- **리랭커 = cross-encoder(BGE-reranker-v2-m3, 한국어)**: top-50 → top-5. 답변품질 **15~30%↑**. + +### 확장 (Phase 2+) +- **Adaptive RAG 라우터**(2026 SOTA): 질의 복잡도 분류 → 단순=하이브리드 / 복잡=다단계 라우팅. +- **CRAG(Corrective RAG)**: 피드백 근거처럼 오답비용 큰 경로에 관련성 그레이딩 + 재질의. +- (선택) **GraphRAG**: 상담이론 교차개념(인간중심↔CBT 연결) 맵이 커질 때만 2차 — 단순 lookup엔 과잉이라 1차 제외. +- (선택) ColBERT 독립 late-interaction: BGE-M3 멀티벡터로 충분, 품질 부족 시에만. + +> 근거 출처: [Anthropic Contextual Retrieval](https://www.anthropic.com/news/contextual-retrieval), [RAG Production Patterns 2026](https://ailearningguides.com/rag-production-patterns-2026/), [Jina ColBERT v2 다국어](https://jina.ai/news/jina-colbert-v2-multilingual-late-interaction-retriever-for-embedding-and-reranking/). Agentic+KG 환각 62%↓(2026.5 MLOps 벤치). + +--- + +## 4. Phase별 구현 로드맵 (단일 로드맵, 산발 task 금지) + +> 각 Phase에 **우선순위·의존성·완료기준(DoD)** 명시. Phase는 순차, 내부 트랙은 병렬(소유 파일 분리). + +### Phase 0 — 기반 정렬 (1~2주차) · 우선순위 P0 +- **목표**: 인프라 스캐폴드 + taxonomy 확정 + IRB 착수 +- **작업**: + - docker-compose 6서비스 스캐폴드 + `secrets/` git-ignore + Caddyfile(`flush_interval -1`) + - Postgres 스키마 §3.2 적용 + RLS 정책 + pgvector + - 라벨 taxonomy v1.0 확정(3축 분리) → 0615 축어록 골든셋 라벨링 1차 + - IRB 프로토콜 초안(Stanford CARE 차용: minimal risk·자살시나리오 1차 제외·전문가 검증 내담자) + - 엔진 어댑터 인터페이스 정의(provider 플래그: claude_api/claude_cli/openai/solar) +- **의존성**: 없음 (시작점). taxonomy ↔ DB 코드테이블 동기화 필요 +- **완료기준**: `docker compose up`으로 6서비스 기동 + Postgres 스키마 마이그레이션 통과 + 골든셋 50발화 라벨링 + IRB 신청서 제출 + +### Phase 1 — MVP 코어 루프 (3~8주차) · 우선순위 P0 +- **목표**: 텍스트 기반 1-페르소나 상담→피드백 1회 완주 (chanpaca.net 시연 가능) +- **트랙 A (백엔드/AI)**: 엔진 어댑터 Messages API 직결 + 캐싱 레이어 + 상태머신(라포→탐색→개입→정리) + 내담자 AI(P1 페르소나) + 평가 AI deep-loop + 입출력 가드레일 +- **트랙 B (프론트)**: `/learn` 셸 + 상담 시뮬레이션 UI 3존(대화60%·피드백패널40%·숨김안전배너) + SSE 스트리밍(React19 `useActionState`/`use`) + 단계바 +- **트랙 C (인프라)**: Cloudflare named tunnel → chanpaca.net + SSE 실측(50분 세션 끊김 테스트) → 끊기면 stream.chanpaca.net Funnel 분리 +- **의존성**: Phase 0 전부. 트랙 A·B는 SSE 스키마·점수 Structured Output 스키마 합의 후 병렬 +- **완료기준**: 교수님이 chanpaca.net에서 P1 내담자와 텍스트 상담 → 회기말 0-5 4차원 피드백+대안발화 수신 → 전 turn이 Postgres에 cost 텔레메트리 포함 적재. **저항 엔진 작동 검증**(공감 시 개방도↑, 조언점프 시↓) + +### Phase 2 — 음성 + 3역할 + 페르소나 확장 (9~15주차) · 우선순위 P1 +- **목표**: 멀티모달 + 3역할 페이지 + 페르소나 3종 + 안전 가드레일 완성 +- **트랙 A (음성)**: 캐스케이드(Deepgram STT → 엔진 → OpenAI TTS), persona→voice 매핑 테이블, 턴테이킹(semantic EOT·침묵 임계 1.2~2.0s), barge-in 로깅 +- **트랙 B (3역할)**: `/teach`(담당 학습자 전체 열람+검수 오버라이드) `/admin`(전부+교수활동 감사) + BFF OAuth 2.1 + RBAC×소유권 이중게이트 +- **트랙 C (AI)**: 페르소나 P2·P3 추가(교수 검수), fast-loop 실시간 태깅, 한국어 자살콘텐츠 분류기(few-shot 5단계) +- **트랙 D (데이터)**: 재귀 학습 파이프라인(annotation_round + IAA 게이트), 교수자 검수 루프(AI vs 교수자 weighted-κ 누적) +- **의존성**: Phase 1 코어 루프. 음성은 transcript 입력레일 위기분류와 결합 필수 +- **완료기준**: 음성 왕복 상담 round-trip ~1.1~2.0s + 3역할 권한 격리 실동작 검증(학습자가 타인 기록 접근 차단) + safety_events 트리거→교수자 자동알림 + 교수 검수 데이터 누적 + +### Phase 3 — IRB 파일럿 + 효과성 검증 (16~20주차) · 우선순위 P1 +- **목표**: 수련생 20명 IRB 파일럿 + 사전사후 효과 측정 + 데이터셋 골든화 +- **작업**: IRB 승인 하 20명 pre-post(자기효능감, 자살/자해 시나리오 1차 제외) + SUS·KPI 측정 + 골든셋 export_manifest → JSONL + 결과 리포트 +- **의존성**: Phase 2 전부 + IRB 승인(Phase 0 신청) +- **완료기준**: 20명 세션 완주 + KPI 충족(임베딩 일관성≥0.79, 환각률≤5%, SUS≥80, κ≥0.6/ICC≥0.75) + 자기효능감 사전사후 유의 + 재귀학습 데이터셋 1차 산출 + +### 1차 명시 제외(2차 백로그) +6-에이전트 풀시스템 · 임상KG 자체구축 · 멀티벤더 A/B · 위기 4~5단계 자유시뮬 · RN 네이티브 앱(1차는 `apps/mobile` 스캐폴드+core import 검증만) · OpenAI Realtime s2s · K8s · Timescale 전면 도입 · 한신대 학내 SSO(1차 Google OIDC 단독) + +--- + +## 5. 기술스택 확정 + +| 계층 | 확정 | 비고 | +|---|---|---| +| 프론트 | **React 19** + Vite 정적빌드, SSE(`useActionState`/`useOptimistic`/`use`), pnpm+Turborepo 모노레포 | `packages/core` 로직/타입/스키마 100% 공유, 추후 RN/Expo | +| 백엔드 | **FastAPI** + uvicorn(`--workers`, SSE 롱리브드 연결 산정) | 상태머신·가드레일·엔진 어댑터·SSE 스트리머 소유 | +| DB | **PostgreSQL 16** + pgvector(RAG 통합), NAS 영구볼륨, RLS | Timescale은 hypertable 선택 확장 | +| 컨테이너 | **Docker Compose** 단일 호스트 모놀리식(6 컨테이너), Caddy 리버스프록시(`flush_interval -1`) | 이식=compose+secrets, 클라우드 VM(국내 리전) `docker compose up` | +| AI 엔진 | **엔진 어댑터** → 기본 Anthropic Messages API(Opus 4.8/Sonnet 4.6), `claude -p`(Max OAuth)는 옵션 플래그 | 과금 누수·동시성·rate limit 흡수. tier: client=Sonnet/Solar, feedback=Opus, cheap=Haiku | +| 음성 | **OpenAI gpt-4o-mini-tts**(13 voice + instructions prosody) + Deepgram STT, voice_id 추상화(Higgs/GPT-SoVITS 로컬 폴백) | s2s 1차 탈락(transcript 보존) | +| 인증 | **OAuth 2.1 Auth Code + PKCE(S256)** BFF, Google OIDC 1차, Authlib | 토큰 Redis만, HttpOnly `__Host-` 쿠키, stateful 세션(IRB 철회 즉시 무효화) | +| 외부노출 | **Cloudflare named tunnel** + SSE는 Tailscale Funnel 분리 | 인바운드 포트 0 | +| 안전 | Presidio PII 마스킹 + NeMo rail + 한국어 자살분류 + 결정론 게이트 | Llama Guard 한국어 미지원 보강 | + +--- + +## 6. 핵심 리스크 + 완화책 + +| # | 리스크 | 영향 | 완화책 | +|---|---|---|---| +| R1 | **`claude -p` OAuth 과금 누수** (이틀 $1,800 사례) | 매칭금 증발 | 기본=Messages API 직결, claude_cli는 옵션 플래그+비용캡 모니터. 어댑터로 무중단 전환 | +| R2 | **Cloudflare SSE 버퍼링·100초 timeout** | 시연 중 스트림 끊김 | named tunnel(일반)+Tailscale Funnel(SSE) 분리, 30초 heartbeat, 전송계층 추상화 | +| R3 | **positivity bias**(저항 내담자 재현 실패) | 시뮬레이션 가치 저하 | 저항을 백엔드 결정론 수치로, Big5 저항 파라미터, CARE-Bench 회귀 테스트 | +| R4 | **CCD 메타노출**("제 핵심신념은…") | 추론 훈련 무력화 | 직접노출 금지 + QA 체크리스트 + Structured Outputs | +| R5 | **자살수단 정보 누출** | 법적 책임·안전 | ideation_stage ≤ 3 상한, 출력 가드레일, 4~5단계는 안전종료 시나리오만 | +| R6 | **LLM-judge 편향**(길이·성별) | 채점 타당도 | raw 직접사용 금지, 길이정규화, 이중코딩 κ≥0.6/ICC≥0.75, 성별 균형 | +| R7 | **PII 외부 LLM 유출** | IRB·법적 책임 | Presidio 마스킹 미들웨어 강제, 마스킹 후만 적재·전송, safety_events 로깅 | +| R8 | **한국어 위기 분류 공백**(Llama Guard 미지원) | 위기 미탐지 | JMIR 2026 한국어 자살콘텐츠 벤치(few-shot 5단계), 재현율 우선 | +| R9 | **한국어 토큰 팽창(최대 35%)** | 비용 과소추정 | 실샘플 token counting, cost_usd 실측 적재 후 재추산, 캐싱 | +| R10 | **NAS 단일 호스트 SPOF** | 시연 중 다운 | 스냅샷 백업, pgdata 정기 덤프, 클라우드 VM 페일오버 리허설 | +| R11 | **한신대 SSO 미확인** | 2단계 일정 불확실 | 1차 Google OIDC 단독, SSO는 Authlib provider 추상화 뒤 2차, 공문 질의 | +| R12 | **20주 일정 압박** | 과대약속 실패 | 1차 범위 통제(2차 백로그 명시), MVP 코어 루프 우선, RN은 스캐폴드만 | + +--- + +## 7. 학술 / IRB 체크리스트 + +**학술 엄밀성** +- [ ] DSM-5 차원 프로파일(RDoC) 반영, 진단명 외부 비노출 가드레일 +- [ ] 가상내담자 충실도: Patient-Ψ(CCD 8요소) + Client101(비네트+메모리) +- [ ] 피드백 타당도: 검증 척도 이식(0-5, CTRS 0-6 분리), 4차원 채점, Structured Outputs +- [ ] 효과성 인과: Stanford CARE RCT(연습+피드백 d=0.72)로 피드백 필수성 입증 → 자유연습 비활성화 +- [ ] LLM-judge 보정 프로토콜(이중코딩 vs LLM κ≥0.6/ICC≥0.75) +- [ ] 인용 논문 마스터 목록: PSI-Bench, PsychEval, TherapyGym/THERAPYJUDGE, Roleplay-doh, CARE-Bench, MIND-SAFE + +**IRB / 윤리** +- [ ] "교육용·비치료용" intended use 고정(FDA General Wellness / 식약처 비의료기기) +- [ ] minimal risk 프로토콜, **자살 시나리오 1차 제외**, 전문가 검증 내담자 +- [ ] 수련생 20명 사전 동의(로그인 직후 하드 게이트, 미동의=시뮬레이션 라우트 서버 차단) +- [ ] 미성년 원본 데이터 4단계 격리, 법정대리인 활용동의 범위 확인 +- [ ] PIPC(2024.12) + 생명윤리법 + 가명처리, 데이터 국내처리/해외전송 명시 +- [ ] D6 컴플라이언스 매트릭스(APA/PIPC/WHO/Character.AI 판례/FDA) +- [ ] **한신대 공문 질의 필요**: SSO 프로토콜/클레임, IRB 주관, 미성년 원본 활용동의, NAS 국내처리 보장 + +**KPI(M5 수용기준)**: 임베딩 일관성≥0.79 · 환각률≤5%(목표≤1%) · Top-1≥0.80 · SUS≥80 · κ≥0.6/ICC≥0.75 · 저항 재현 · 자기효능감 사전사후 + +--- + +## 8. 즉시 착수할 다음 액션 Top 5 + +1. **한신대 공문 질의서 발송** — SSO 프로토콜/클레임, IRB 주관 기관, 미성년 원본 사례데이터 법정대리인 활용동의 범위, NAS Postgres 국내처리 보장. (트웬티온스→한신대, 가장 긴 리드타임·블로킹 의존성) +2. **IRB 프로토콜 초안 작성·제출** — Stanford CARE 차용(minimal risk, 자살시나리오 1차 제외, 전문가 검증 내담자, 수련생 20명 사전동의). 승인 리드타임이 Phase 3 게이트. +3. **docker-compose 6서비스 스캐폴드 + Postgres 스키마 §3.2 + Caddyfile(`flush_interval -1`)** — 기존 풀스택팀에 인프라 베이스 인계. 이후 트랙 병렬화의 기반. +4. **0615 축어록 라벨 taxonomy v1.0 확정 + 골든셋 1차 라벨링** — 3축 분리(기법/내담자상태/슈퍼바이저논평) 코드테이블화. 평가 AI 정답셋 + 페르소나 상태전이 정답으로 이중 활용. +5. **엔진 어댑터 PoC(Messages API 기본 + 캐싱 레이어 + Presidio 마스킹 선행)** — `claude -p` 과금 누수 회피 검증 + P1 페르소나 1턴 응답 + 비용 텔레메트리 실측. claude_cli는 옵션 플래그로 병존. + +--- + +## 부록 A. 도메인 설계서 원본 (절대경로) + +- 학술근거·평가: `Projects/영업/2026/한신대학교 산학협력 프로젝트/설계서/01_학술근거_평가프레임워크_설계서.md` +- DSM-5 페르소나(M1): `…/scratchpad/M1_persona_engine_design.md` +- taxonomy·annotation: `…/_analysis/상담기법-슈퍼비전-taxonomy-annotation-스키마-설계서.md` +- 3-AI 오케스트레이션: `…/설계/3AI_오케스트레이션_아키텍처_설계서.md` +- 데이터모델·NAS Postgres: `Projects/수업/2026/한신대 SW중심대학 산학협력 2026/설계/데이터모델_NAS_Postgres_스키마_설계서.md` +- 인증·RBAC·IRB: `…/설계/auth-rbac-설계서.md` +- 실시간 음성 채팅: `…/설계/[설계서] 실시간 음성 채팅 아키텍처.md` +- 프론트엔드·UX: `…/설계/프론트엔드-UX-설계서.md` +- 배포·인프라: 본 마스터플랜 §1·§4·§5에 통합 (별도 파일 미생성) + +## 부록 B. 디자인 철칙 (윤찬, 전 화면 강제) +border-left 강조선 · 이모지(SVG 내부 포함) · AI 양산형 카드덤프 · 무지개 점수색 **전부 금지**. 에디토리얼 미니멀, 한 페이지 한 메시지, 강조는 weight/배경틴트/상단 kicker. 채도 낮은 그린-그레이 신뢰 톤. (린트 강제 권장) diff --git a/docs/MEMORY_KNOWLEDGE_PERSONA_DESIGN.md b/docs/MEMORY_KNOWLEDGE_PERSONA_DESIGN.md new file mode 100644 index 0000000..11987e1 --- /dev/null +++ b/docs/MEMORY_KNOWLEDGE_PERSONA_DESIGN.md @@ -0,0 +1,602 @@ +# 메모리·지식·페르소나 통합 아키텍처 설계서 + +> 한신대 AI 심리상담 시뮬레이션 훈련 플랫폼 · **단일 종합 설계서** +> 작성 2026-06-25 · 트웬티온스 산학협력 +> 종합 대상 4개 영역: ① 회기 간 연속성 메모리(최우선 갭) · ② 지식 데이터베이스(KB) · ③ 페르소나 일관성 · ④ 3-AI 메모리 뷰/정보비대칭/종단평가 +> 상위 SoT: `HANSHIN_AI_PLATFORM_MASTERPLAN.md` + `MASTERPLAN_REVISIONS.md`(BLUE TEAM 패치). **본 문서는 두 상위 문서의 확정 전제를 깨지 않는 하위 통합 설계다.** + +--- + +## 0. 확정 전제 (바꾸지 않음) + 설계 철학 + +### 0.1 변경 불가 기술 전제 + +| 항목 | 확정값 | +|---|---| +| 엔진 | 로컬 `claude -p` 상주 멀티턴 풀(`--input-format stream-json`), Messages API는 가용성 폴백 | +| RAG | BGE-M3(dense+sparse+ColBERT 단일모델) + pgvector(HNSW) + 하이브리드 + Anthropic Contextual Retrieval + BGE-reranker-v2-m3 | +| DB | NAS PostgreSQL 16 + pgvector, RLS 이중강제 | +| 백엔드 | FastAPI(상태머신·가드레일·엔진 어댑터 소유) | +| 3-AI | 상담사AI(보조/선택) · 내담자AI · 백그라운드 평가AI | +| 정보비대칭 | DB `visible_to[]` 컬럼이 강제(프롬프트 신뢰 X), 인간 RBAC×cohort와 2-레이어 AND | + +### 0.2 통합 설계 철학 — 람다 자신의 메모리 시스템에서 차용한 3대 원리 + +이 플랫폼의 메모리·지식·페르소나는 **람다(이 AI)의 실제 운영 메모리 구조**를 1차 레퍼런스로 삼는다. 검증된 운영 패턴이라 추측이 아니다. + +| 람다 시스템 원리 | 통합 설계 적용 | +|---|---| +| **계층 압축** (daily 원본 → weekly 요약 → monthly 요약, 원본 archive 절대보존, 숫자 무손실) | 메모리 4계층(working/episodic/summary/semantic) + KB 3계층(청크/Contextual prefix/community 요약). 원본 append-only, 압축은 파생 테이블에만 | +| **역할 분리** (rag-memory=BGE-M3 sqlite 시맨틱 ↔ graphify=GraphRAG 구조, "둘 다 유지, 충돌 X, 질문 유형에 맞게 선택") | 에피소드 메모리(내담자 일관성) ↔ 정적 지식 KB를 **물리적으로 다른 스키마·다른 검색정책**으로 분리. 충돌 시 우선순위 룰로 결정 | +| **부트의 큰그림→세부 읽기** (monthly→weekly→daily, "본문 인용 필요할 때만 직접 Read") | 회기 시작 회상도 case_digest(큰그림)→직전 summary(중간)→episodic recall(세부) 순으로 토큰 예산 배분. 매 발화 전체 덤프 금지 | +| **IDENTITY/SOUL 불변 + daily 가변** (정체성이 일지 압축에 오염 안 됨) | 페르소나 = 불변 카드(persona_card) + 가변 메모리(persona_session_memory)를 다른 테이블·다른 프롬프트 레이어·다른 권한으로 물리 분리 → drift 구조적 차단 | + +### 0.3 4대 대원칙 (전 영역 관통) + +- **P1. 원본은 절대 삭제 안 한다** — `turns`는 append-only(람다 archive). "망각"은 물리삭제가 아니라 회상 우선순위 배제(salience 하위 → 검색 랭킹 하위 → system 미주입). +- **P2. 숫자(상태 수치)는 무손실로 넘긴다** — working state(저항·라포·openness)는 LLM 요약이 아니라 **결정론 상태머신이 DB 컬럼으로** 직접 carry-over. LLM에 수치를 맡기면 hallucination. +- **P3. 큰 그림 → 세부 순서로 회상** — 토큰 예산을 case→summary→episodic 순으로 배분. +- **P4. 압축은 LLM, carry-over는 결정론** — 서사(narrative)는 LLM 압축(손실 허용), 상태 수치·pinned fact는 코드가 직접 복사(무손실). + +--- + +## 1. 통합 메모리 계층도 (working/episodic/semantic/summary + 지식KB) + +``` +═══════════════════════════════════════════════════════════════════════════════ + 통합 메모리·지식 아키텍처 — 5개 저장 계층 (메모리 4 + 지식 1), 물리 분리 +═══════════════════════════════════════════════════════════════════════════════ + + 계층 수명 저장소(스키마.테이블) 채우는 주체 람다 대응물 +───────────────────────────────────────────────────────────────────────────────── + ┌──── 에피소드 메모리 (내담자 연속성) ── app 스키마 ────────────────────────────┐ + │① WORKING 회기 내 app.session_state 상태머신(결정론) L3+L4 │ + │ 회기내상태 (체크포인트) +단기버퍼(JSONB) 매 턴 UPSERT 상태머신 │ + │ │ + │② EPISODIC 영구 app.turns + 발화 로깅(원자) daily │ + │ 회기별사건 (append-only) app.turn_embedding(HNSW) 회기 중 임베딩 (원본) │ + │ │ + │③ SUMMARY 영구 app.session_summary 회기종료 압축(LLM) weekly │ + │ 회기종료요약 (+digest 임베딩) 회기당 1행 │ + │ │ + │④ SEMANTIC 영구(evolving) app.case_profile 케이스누적 압축 monthly │ + │ 누적임상프로파일 + app.pinned_fact 회기마다 갱신/병합 +MEMORY │ + └───────────────────────────────────────────────────────────────────────────────┘ + ┌──── 정적 지식 KB (DSM/이론/taxonomy) ── kb 스키마 (독립, FK 없이 read-only) ──┐ + │⑤ KNOWLEDGE 영구(버전드) kb.chunk(멀티벡터 HNSW) 오프라인 인덱싱 graphify │ + │ 정적임상지식 +kb.document +kb.source (배치, 런타임 아님) references│ + │ 3계층: 원본청크 → Contextual prefix → community 요약(예산초과 fallback) │ + └───────────────────────────────────────────────────────────────────────────────┘ + +───────────────────────────────────────────────────────────────────────────────── + 데이터 흐름: + 회기종료: ②turns ──압축(LLM)──► ③session_summary ──병합(LLM+rule)──► ④case_profile + + pinned_fact(rule) + 회기시작: ④case_profile+pinned ──┐ + ③직전 summary ──┼─로드(큰그림→세부)──► 내담자AI system L2-EP + ②episodic recall(top-k)─┘ (BGE-M3 하이브리드+reranker, ~1.5~2K토큰) + ⑤KB 청크 ───────────────────로드(정책별 필터)──► system L2-KB (캐시친화) + + 모순 시 우선순위: pinned(L4) > 에피소드(이미 한 말) > 상태머신수치 > KB > LLM사전지식 + ("KB는 사전이지 판사가 아니다" — 내담자는 에피소드를 따르고 KB는 보정용) +═══════════════════════════════════════════════════════════════════════════════ +``` + +### 1.1 계층별 역할 한 줄 정의 + +- **① WORKING**: 상태머신 결정론 수치(stage·effective_openness·rapport_credit·resistance·ideation_stage) + 최근 K턴 버퍼. 매 턴 체크포인트 UPSERT(프로세스 죽어도 복원). +- **② EPISODIC**: 발화=최소 원자단위, 저장 시 BGE-M3 임베딩 → 회기 시작 회상의 검색 대상. append-only(재귀학습·audit 원천). +- **③ SUMMARY**: 회기당 1행. 구조화 수치 snapshot(무손실) + narrative(LLM 압축) 분리. 다음 회기 carry-over 1차 단위. +- **④ SEMANTIC**: 케이스 전체 궤적 누적 압축(CCD 추정·호소문제·라포궤적·치료동맹). pinned_fact=절대 흘리면 안 되는 핵심(모순 처리 기준점). +- **⑤ KNOWLEDGE**: DSM/이론/taxonomy/논평. 버전드, 거의 불변. 3-AI가 같은 물리 테이블을 다른 정책으로 검색. + +--- + +## 2. 회기 라이프사이클 시퀀스 + +``` +═══════════════════════════════════════════════════════════════════════════════ + 회기 라이프사이클: 회기시작 회상 → 진행(턴 사이클) → 회기종료 압축 → 다음 회기 +═══════════════════════════════════════════════════════════════════════════════ + +【A. 회기 시작 — 회상 + 상태 복원】 (람다 부트 monthly→weekly→daily 차용) +──────────────────────────────────────────────────────────────────────────── + 학습자가 P1 내담자와 N번째 회기 시작 + │ + ├─1. case_profile + pinned_fact 로드 (큰 그림, ~400+300토큰) [P3] + │ load_pinned_facts(case_id, status IN stable/evolving/locked) + │ + ├─2. 직전 session_summary 로드 (중간, ~500토큰) + │ {digest, open_threads, homework, end_state} + │ + ├─3. episodic recall (세부, top-5, ~600토큰) + │ query = open_threads + homework (+학습자 첫 발화) + │ → hybrid_search(case_id 스코프, dense+sparse RRF, top-50) + │ → BGE-reranker-v2-m3 → top-5 + │ + ├─4. 상태 복원 (carry-over, 결정론) [P2] + │ init_session_state_from_history(): + │ stage = '라포' (매 회기 라포부터 재시작 — 상담 구조) + │ rapport_credit = prev.rapport_credit × 0.7 (주1회 간격 일부 이월) + │ resistance = inter_session_drift(prev, alliance) + │ ideation_stage = prev (보수적 유지 — 안전 R5) + │ + ├─5. KB 정적 청크 로드 (페르소나 증상 단서, 정책 필터) + │ visible_to='client' AND sensitivity<=1, 본문 비노출(행동단서만) + │ + └─6. 상주 claude -p 프로세스 점유 + system 주입 (불변, cache_control) + L0 가드레일 + L1 페르소나카드(불변) + L2-EP 회상 + L2-KB 지식 + → session_state.recall_context에 캐시 (회기 내 재검색 0) + +【B. 회기 진행 — 턴 사이클】 (마스터플랜 §2.2, 매 발화) +──────────────────────────────────────────────────────────────────────────── + 학습자 발화 U_t + │ + ├─ [입력 가드레일] Presidio PII 마스킹 + 위기분류(실제위기 vs 연기 분기) + ├─ [라우터] route_memory_vs_knowledge(turn, ai_role) [람다 "필요할때만 Read"] + │ needs_episodic = 지시어("아까/저번에") OR role=client OR 단계전이 + │ needs_knowledge = role=evaluator OR 임상용어 OR role=counselor + ├─ [상태머신] effective_openness(t) 계산 (결정론, LLM 아님) [P2] + │ = clamp(stage.openness + rapport_credit×unlock_rate - resistance×decay) + ├─ [모순 검사] handle_potential_contradiction(new_turn, pinned) + │ locked 사실모순 → 차단·재생성 / clinical 변화 → 수용·갱신+이력 + ├─ [내담자 AI] stdin JSON 주입 → 페르소나 응답 (CCD 직접노출 금지) + │ L3 상태수치 + L4 fact hard-pin + L5 episodic recall + L6 최근N턴 + ├─ [출력 가드레일] 자살수단 차단, ideation≤3, drift 감지(임베딩 일관성≥0.79) + ├─ [평가 AI 2-tier] + │ fast-loop(로컬/Haiku): U_t만 4차원 태깅 (실시간) + │ deep-loop(Opus 상주): 단계전환·회기말 0-5 채점 + 대안발화 + ├─ [working 갱신] session_state UPSERT (체크포인트, 단기버퍼 append) + └─ [로깅] turns → Postgres + 임베딩 (재귀학습 JSONL 원천) + ↑ K턴 반복 (recall_context는 회기 내 재사용, 재검색 안 함) + +【C. 회기 종료 — 압축】 (람다 daily→weekly 컴프레션, 비동기 비블로킹) +──────────────────────────────────────────────────────────────────────────── + stage='정리' 완료 OR 명시 종료 + │ + ├─(A) 무손실 carry-over: end_state = working 종료 snapshot (코드 복사, LLM 미경유) [P4] + ├─(B) salience 산출 → 망각/유지 결정 + │ compute_salience(t): pinned/위기/최초개방/정서급변 ↑, 잡담 ↓ + │ salience ≥ 0.5 → episodic 영구유지(검색 우선), 미만 → digest 흡수 + ├─(C) narrative 압축(LLM, 상주 claude -p 재사용): digest 6~10문장 + open_threads + │ pinned 사실은 프롬프트 hard-pin → digest 보존 강제 + │ 입력 = turns.text_masked만 (F-03 마스킹 강제) + ├─(D) digest 임베딩 + session_summary UPSERT + episodic 임베딩 백필 + ├─(E) case_profile 병합: rapport_trajectory append, CCD 추정 갱신, + │ case_digest 재압축(N회기마다 or major shift), alliance EWMA + ├─(F) pinned_fact 모순/갱신 처리 + pinned_fact_history 이력화 + └─(G) RAG 동기화 (람다 storeDocument→embed): 다음 회기 회상 대비 + │ + ├─ 상주 프로세스 회수 (누적 컨텍스트 리셋 → 말투표류 D-c 회기경계서 차단) + └─ 종단 평가: learner_profile EWMA(현재수준)+slope(성장속도) 갱신 + +【D. 다음 회기】 → 【A】로 복귀 (case_profile·summary·pinned 누적된 상태로) + 토큰 ~4K 고정 (100회기여도 선형증가 X) +═══════════════════════════════════════════════════════════════════════════════ +``` + +--- + +## 3. 통합 Postgres 스키마 (메모리·지식·페르소나·평가, DDL 수준) + +> 스키마 4분할: `app`(운영·메모리·페르소나) / `kb`(정적 지식, 독립) / `audit`(append-only 감사) / `ds`(재귀학습). **Phase 0에 전부 선반영(nullable), cross-session·KB 채우기는 Phase 2a**(MASTERPLAN_REVISIONS §3.5 재마이그레이션 폭탄 방지 원칙). + +### 3.1 페르소나 — 불변 카드 (app.persona_card) + +```sql +-- ════ 불변 정체성 (람다 IDENTITY/SOUL, 버전드, 교수만 편집) ════ +CREATE TABLE app.persona_card ( + persona_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + code TEXT NOT NULL, -- 'P1','P2','P3' + version INT NOT NULL DEFAULT 1, -- 편집 시 +1 (진행 세션은 옛 version 핀) + status TEXT NOT NULL DEFAULT 'draft' + CHECK (status IN ('draft','review','approved','archived')), + display_name TEXT NOT NULL, + difficulty TEXT NOT NULL CHECK (difficulty IN ('easy','moderate','hard')), + theory_target TEXT[], -- {'humanistic','cbt'} + demographics JSONB NOT NULL, -- 범주화(재식별 방지 F-25): age_band,sex,grade... + presenting JSONB NOT NULL, -- 표층 호소(내담자가 입으로 말함) + history JSONB NOT NULL, -- 과거사 + big5 JSONB NOT NULL, -- {O,C,E,A,N} 0~1 (말투·반응 수학적 앵커) + resistance JSONB NOT NULL, -- {base_resistance,unlock_rate,decay_floor,silence_prob,deflection_prob} + speech_style JSONB NOT NULL, -- {register,avg_sentence_len,fillers,honorific,verbal_tics} + affect_baseline JSONB NOT NULL, -- {negative_affect,hopelessness,anhedonia,sleep,anxiety,suicide_ideation_stage} hard상한=3 + ccd JSONB NOT NULL, -- Patient-Ψ 8요소 (★직접발화 금지 R4, 행동으로만) + dsm5_dimensional JSONB NOT NULL, -- criteria_behavior_matrix (진단명 비노출, 내부 엄밀성 F-22) + source_provenance TEXT NOT NULL, -- '0615 합성변형' (원문 미적재 F-05) + is_synthetic BOOL NOT NULL DEFAULT TRUE, + created_by UUID, approved_by UUID, approved_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + UNIQUE (code, version) +); +CREATE INDEX idx_persona_card_status ON app.persona_card(status) WHERE status='approved'; + +-- 음성 매핑 (provider-agnostic, 카드 버전 정합) +CREATE TABLE app.persona_voice_map ( + persona_id UUID NOT NULL REFERENCES app.persona_card(persona_id) ON DELETE CASCADE, + version INT NOT NULL, + voice_id TEXT NOT NULL, -- 'voice_p1_teen_m' + provider TEXT NOT NULL CHECK (provider IN ('openai','higgs','melotts')), + base_params JSONB NOT NULL, + prosody_map JSONB NOT NULL, -- 상태(stage/affect/resistance)→prosody 함수 + PRIMARY KEY (persona_id, version) +); + +-- 상담사 AI 페르소나 (데모/self-play용, 이론 충실도 일관) +CREATE TABLE app.counselor_profile ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), code TEXT, + theory TEXT CHECK (theory IN ('humanistic','cbt','integrative')), + skill_level TEXT, reasoning_chain JSONB, -- CoE: PCT/CBT 추론체인 + allowed_microskills TEXT[], speech_style JSONB +); +``` + +### 3.2 메모리 ① WORKING (app.session_state, 체크포인트) + +```sql +CREATE TABLE app.session_state ( + session_id UUID PRIMARY KEY REFERENCES app.sessions(id) ON DELETE CASCADE, + stage TEXT NOT NULL DEFAULT '라포' CHECK (stage IN ('라포','탐색','개입','정리')), + turn_seq INT NOT NULL DEFAULT 0, + effective_openness REAL NOT NULL DEFAULT 0.15 CHECK (effective_openness BETWEEN 0 AND 1), + rapport_credit REAL NOT NULL DEFAULT 0.0, -- 회기말 carry 대상 (0.7 이월) + resistance REAL NOT NULL DEFAULT 0.65 CHECK (resistance BETWEEN 0 AND 1), + ideation_stage SMALLINT NOT NULL DEFAULT 1 CHECK (ideation_stage BETWEEN 1 AND 5), + affect_state JSONB NOT NULL DEFAULT '{}', + short_buffer JSONB NOT NULL DEFAULT '[]', -- 최근 K턴 rolling window + recall_context JSONB, -- 회기시작 1회 로드, 회기내 재사용 캐시 + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +``` + +### 3.3 메모리 ② EPISODIC (app.turns 확장 + app.turn_embedding) + +```sql +-- 마스터플랜 §3.2 turns에 연속성 컬럼 추가 +ALTER TABLE app.turns + ADD COLUMN IF NOT EXISTS salience REAL DEFAULT 0.0, -- 망각/우선순위 + ADD COLUMN IF NOT EXISTS is_pinned BOOL DEFAULT FALSE, -- pinned_fact 승격 발화 + ADD COLUMN IF NOT EXISTS contradicts UUID[] DEFAULT '{}', -- 모순추적: 뒤집은 과거 turn_id[] + -- 음성 paralinguistic (MASTERPLAN_REVISIONS §3.5 선반영) + ADD COLUMN IF NOT EXISTS audio_ref TEXT, + ADD COLUMN IF NOT EXISTS silence_ms INT, + ADD COLUMN IF NOT EXISTS speech_rate REAL, + ADD COLUMN IF NOT EXISTS barge_in BOOL; + +CREATE TABLE app.turn_embedding ( + turn_id UUID PRIMARY KEY REFERENCES app.turns(id) ON DELETE CASCADE, + case_id UUID NOT NULL, -- denormalized: 케이스 스코프 회상 + session_id UUID NOT NULL, seq INT NOT NULL, + dense vector(1024) NOT NULL, -- BGE-M3 dense + sparse JSONB, -- BGE-M3 sparse {token_id:weight} + context_prefix TEXT, -- Contextual Retrieval 1문장 + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +CREATE INDEX idx_turn_emb_hnsw ON app.turn_embedding + USING hnsw (dense vector_cosine_ops) WITH (m=16, ef_construction=64); +CREATE INDEX idx_turn_emb_case ON app.turn_embedding (case_id, session_id); +``` + +### 3.4 메모리 ③ SUMMARY (app.session_summary) + +```sql +CREATE TABLE app.session_summary ( + session_id UUID PRIMARY KEY REFERENCES app.sessions(id) ON DELETE CASCADE, + case_id UUID NOT NULL, session_no INT NOT NULL, + -- (A) 무손실 carry-over (상태 수치 snapshot, LLM 미경유) [P2] + end_state JSONB NOT NULL, -- {stage,openness,rapport_credit,resistance,ideation_stage,affect} + rapport_delta REAL, + -- (B) narrative 압축 (LLM, 손실 허용) + digest TEXT NOT NULL, -- 6~10문장, 다음 회기 system 주입 + open_threads JSONB NOT NULL DEFAULT '[]', -- 미해결 [{topic,last_stance,raised_session_no}] + homework JSONB, emotional_arc TEXT, + -- (C) 검색용 + digest_embedding vector(1024), + token_count INT, compressed_by TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + UNIQUE (case_id, session_no) +); +CREATE INDEX idx_sess_sum_case ON app.session_summary (case_id, session_no DESC); +CREATE INDEX idx_sess_sum_emb ON app.session_summary + USING hnsw (digest_embedding vector_cosine_ops) WITH (m=16, ef_construction=64); +``` + +### 3.5 메모리 ④ SEMANTIC (app.case_profile + app.pinned_fact) + +```sql +-- 케이스 = 한 가상내담자의 다회기 전체 (persona_id=템플릿, case=학습자별 인스턴스) +CREATE TABLE app.case_profile ( + case_id UUID PRIMARY KEY, + persona_id TEXT NOT NULL, -- 시드 페르소나 + learner_id UUID NOT NULL, -- ★ 학습자별 독립 연속체 + last_session_no INT NOT NULL DEFAULT 0, + ccd_estimate JSONB NOT NULL DEFAULT '{}', -- CCD 8요소 현재추정 +confidence + presenting_arc JSONB NOT NULL DEFAULT '[]', -- 호소문제 궤적 + rapport_trajectory JSONB NOT NULL DEFAULT '[]', -- [{session_no,end_rapport,end_openness}] + alliance_level REAL DEFAULT 0.2, -- 치료동맹 누적 + case_digest TEXT NOT NULL DEFAULT '', -- 전체 궤적 8~12문장 (큰그림, 람다 monthly) + digest_embedding vector(1024), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- pinned facts (람다 MEMORY.md, 절대 흘리면 안 되는 핵심, 모순 기준점) +CREATE TABLE app.pinned_fact ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + case_id UUID NOT NULL REFERENCES app.case_profile(case_id) ON DELETE CASCADE, + key TEXT NOT NULL, -- 'name','family','suicide_attempt_history' + value TEXT NOT NULL, -- 가명처리/마스킹된 + fact_type TEXT NOT NULL CHECK (fact_type IN ('identity','history','relationship','clinical','agreement')), + status TEXT NOT NULL DEFAULT 'stable' CHECK (status IN ('stable','evolving','contradicted','locked')), + source_turn UUID, confidence REAL NOT NULL DEFAULT 1.0, + version INT NOT NULL DEFAULT 1, updated_session_no INT, + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + UNIQUE (case_id, key) +); +CREATE INDEX idx_pinned_case ON app.pinned_fact (case_id, status); + +-- pinned 변경 이력 (append-only, 모순/갱신 audit + 평가AI 정답신호) +CREATE TABLE app.pinned_fact_history ( + id BIGSERIAL PRIMARY KEY, fact_id UUID NOT NULL, case_id UUID NOT NULL, + old_value TEXT, new_value TEXT, + reason TEXT, -- 'contradiction','clarification','progression' + session_no INT, turn_id UUID, created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +``` + +### 3.6 지식 ⑤ KB (kb 스키마, 독립) + +```sql +CREATE SCHEMA kb; +CREATE EXTENSION IF NOT EXISTS vector; +CREATE EXTENSION IF NOT EXISTS pg_trgm; + +-- 출처 + 라이선스 (DSM-5 저작권 1차 통제) +CREATE TABLE kb.source ( + source_id TEXT PRIMARY KEY, -- 'dsm5','theory','taxonomy_0615' + title TEXT NOT NULL, + kb_kind TEXT NOT NULL CHECK (kb_kind IN + ('diagnostic','theory','technique','taxonomy','supervisor_pattern','template','ko_context')), + license_class CHAR(1) NOT NULL CHECK (license_class IN ('A','B','C','D')), -- C=저작권민감 D=미성년파생 + origin_path TEXT, citation TEXT, + external_llm_ok BOOLEAN NOT NULL DEFAULT false, -- C/D면 false → 국내 라우팅 + created_at TIMESTAMPTZ DEFAULT now() +); + +-- 문서 + 버전 (graphify content_hash 증분 인덱싱 차용) +CREATE TABLE kb.document ( + doc_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + source_id TEXT NOT NULL REFERENCES kb.source(source_id), + doc_uri TEXT NOT NULL, version INT NOT NULL DEFAULT 1, + content_hash TEXT NOT NULL, -- SHA256 → 변경감지(증분) + superseded_by BIGINT REFERENCES kb.document(doc_id), + is_active BOOLEAN NOT NULL DEFAULT true, indexed_at TIMESTAMPTZ, + UNIQUE (source_id, doc_uri, version) +); + +-- 청크 + 멀티벡터 (검색 본체) +CREATE TABLE kb.chunk ( + chunk_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + doc_id BIGINT NOT NULL REFERENCES kb.document(doc_id) ON DELETE CASCADE, + source_id TEXT NOT NULL, kb_kind TEXT NOT NULL, -- 비정규화(필터 가속) + seq INT NOT NULL, heading_path TEXT, + chunk_text TEXT NOT NULL, -- 표시·LLM 주입(prefix 미포함) + context_prefix TEXT, -- Contextual Retrieval 주입문 + embedding vector(1024), -- BGE-M3 dense + sparse_vec JSONB, colbert_vecs JSONB, -- 멀티벡터(단일 호출 산출) + visible_to TEXT[] NOT NULL DEFAULT '{client,counselor,evaluator}', -- 정보비대칭 + sensitivity SMALLINT NOT NULL DEFAULT 0, -- 0공개 1비공개내부 2평가전용 3원천격리 + label_id BIGINT, -- taxonomy면 technique_label_def FK + meta JSONB DEFAULT '{}', -- {dsm_category,theory,bias_weight...} + token_count INT, created_at TIMESTAMPTZ DEFAULT now() +); +CREATE INDEX idx_chunk_hnsw ON kb.chunk USING hnsw (embedding vector_cosine_ops) WITH (m=16, ef_construction=64); +CREATE INDEX idx_chunk_fts ON kb.chunk USING gin (to_tsvector('simple', chunk_text)); +CREATE INDEX idx_chunk_trgm ON kb.chunk USING gin (chunk_text gin_trgm_ops); +CREATE INDEX idx_chunk_visible ON kb.chunk USING gin (visible_to); +CREATE INDEX idx_chunk_route ON kb.chunk (kb_kind, sensitivity); +``` + +### 3.7 평가 + 종단 추적 + 드리프트 감사 (app + audit) + +```sql +-- 발화별 평가 (마스터플랜 §3.2, visible_to로 학습자 비노출) +CREATE TABLE app.feedback_scores ( + turn_id UUID NOT NULL, dimension TEXT NOT NULL, -- 공감/개방질문/검증/이론부합 + score REAL, -- 0-5 (CTRS 0-6) + rationale TEXT, top1_score REAL, -- CRAG 게이트(임계미달→관찰프레이밍 F-06) + visible_to TEXT[] NOT NULL DEFAULT '{evaluator}', + PRIMARY KEY (turn_id, dimension) +); + +-- 종단 학습자 프로파일 (회기 거듭하며 나아지는지 — 윤찬 직접 답) +CREATE TABLE app.learner_profile ( + learner_id UUID PRIMARY KEY, + -- 4차원 EWMA(현재수준) + slope(성장속도) 동시 + dim_ewma JSONB NOT NULL DEFAULT '{}', -- {empathy:0.6, open_q:0.5, validation:..., theory:...} + dim_slope JSONB NOT NULL DEFAULT '{}', -- 성장속도 (잘하는데정체 vs 못하지만급성장 구분) + persistent_gaps JSONB DEFAULT '[]', -- deep-loop만 맥락화 코칭 (fast-loop 절대 미주입) + session_count INT DEFAULT 0, updated_at TIMESTAMPTZ DEFAULT now() +); + +-- 페르소나 드리프트 감사 (KPI 임베딩일관성≥0.79 측정) +CREATE TABLE audit.persona_drift_log ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + session_id UUID NOT NULL, turn_id UUID, persona_id UUID NOT NULL, + drift_type TEXT NOT NULL, -- 'big5_violation'|'ccd_leak'|'fact_contradiction'|'style_drift' + embedding_sim REAL, detail JSONB, + severity TEXT CHECK (severity IN ('warn','block')), + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- KB 검색 로그 (환각측정·캐시검증, 람다 cost.json 차용) +CREATE TABLE kb.retrieval_log ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + session_id UUID, turn_id UUID, ai_role TEXT NOT NULL, + query_text TEXT, policy TEXT NOT NULL, hit_chunk_ids BIGINT[], + rerank_scores REAL[], top1_score REAL, used_in_answer BOOLEAN, + latency_ms INT, created_at TIMESTAMPTZ DEFAULT now() +); +``` + +> **핵심 설계 결정 — `(persona_id, learner_id)` 복합키**: 한 페르소나(P1)를 20명이 각자 연습한다. 같은 카드(불변) + 다른 연속체(가변). `case_profile.learner_id`가 "이 학습자에게 이 내담자가 겪은 회기 히스토리"를 정의 → 학습자 간 기억 오염 차단. + +--- + +## 4. 3-AI 정보비대칭이 메모리·KB 레이어에서 강제되는 방식 + +### 4.1 핵심 원리 — 위험한 건 turn 표면이 아니라 파생 메모리 + +turn 자체는 대화 표면이라 덜 위험하다. **위험한 건 CCD·정답라벨·점수·종단요약 같은 파생 메모리.** 여기에만 `visible_to[]`를 달고, **컨텍스트 조립기가 역할별로 물리적으로 다른 쿼리 분기**를 탄다. + +``` +2-레이어 강제 (MASTERPLAN_REVISIONS §5.1 F-30): + ┌─ 레이어1: AI 정보비대칭 (current_ai_view enum) ── visible_to[] WHERE 강제 + │ CLIENT_AI 분기엔 load_ccd()·load_evaluator_answer() 함수 자체가 없음 + │ → "필터 누락"이 아니라 "코드 경로 부재"가 1차 방어 + └─ 레이어2: 인간 RBAC×cohort (current_role) ── RLS DB레벨 방어선 + 인간이 turn 읽을 때 두 게이트 AND (단일 미들웨어 + RLS 이중강제) +``` + +### 4.2 메모리 레이어 — 3-AI 뷰 격리 매트릭스 + +| 메모리/KB 항목 | ② 내담자 AI | ① 상담사 AI(보조) | ③ 평가 AI | +|---|---|---|---| +| ① working(상태수치) | 자기 stage·openness만 (수치 주입, 메타노출 X) | 표면 대화만 | 전체 (저항엔진 검증) | +| ② episodic(자기 발화) | 자기 케이스 recall만 (case 스코프 강제) | 회상 없음(표면만) | 전체 (정답 대조) | +| ③ session_summary | digest·pinned는 "자기기억"으로만 표현 | 미노출 | 전체 + open_threads | +| ④ case_profile.ccd_estimate | **절대 비노출** (R4 메타노출 금지) | **절대 비노출** | 전체 (정답 CCD) | +| ④ pinned_fact(clinical) | 자기 사실로만 | 미노출 | 전체 (변화 정답신호) | +| ⑤ KB diagnostic(DSM) | 내부 파라미터 (본문 비노출, 행동단서만) | **차단** | 전체 | +| ⑤ KB taxonomy 정답라벨 | 차단 (sensitivity≥2) | 차단 | 전체 (채점 기준) | +| learner_profile | 차단 | 차단 | deep-loop만 (fast 미주입, 사전편향 차단) | + +### 4.3 KB 레이어 — 같은 물리 테이블, 다른 retrieval 정책 4-튜플 + +검색 정책 = **사전필터(WHERE) + 회수 가중치 + 리랭킹 목표 + 주입 방식**. + +| 차원 | ② 내담자 AI | ① 상담사 AI | ③ 평가 AI | +|---|---|---|---| +| 사전필터 | `kb_kind IN (diagnostic,theory,technique)` `AND 'client'=ANY(visible_to)` `AND sensitivity<=1` | `kb_kind IN (theory,technique,microskill,ko_context)` `AND 'counselor'=ANY(visible_to)` `AND sensitivity=0` | `'evaluator'=ANY(visible_to)` `AND sensitivity<=2` (taxonomy·supervisor 정답 포함) | +| 회수 가중치 | dense 0.7 / sparse 0.3 (증상 의미) | dense 0.5 / sparse 0.5 (기법명) | sparse 0.6 / dense 0.4 (라벨명·논평) | +| 주입 방식 | 본문 비노출, "행동단서 요약"만 | 본문+예시 | 본문+label_id+bias_weight | +| 절대 차단 | sensitivity≥2, 타 페르소나 CCD | DSM diagnostic, 평가정답 | (전부 봄, 학습자 비노출은 응답 가공단) | + +```sql +-- 하이브리드 검색 SQL 계약 (정책별 파라미터화, visible_to DB 강제) +WITH dense AS ( + SELECT chunk_id, 1-(embedding <=> :q_dense) AS s_dense FROM kb.chunk + WHERE kb_kind=ANY(:kinds) AND :role=ANY(visible_to) AND sensitivity<=:sens_max + ORDER BY embedding <=> :q_dense LIMIT 50), +sparse AS ( + SELECT chunk_id, ts_rank_cd(to_tsvector('simple',chunk_text),:q_terms) AS s_sparse FROM kb.chunk + WHERE kb_kind=ANY(:kinds) AND :role=ANY(visible_to) AND sensitivity<=:sens_max + AND to_tsvector('simple',chunk_text) @@ :q_terms LIMIT 50) +SELECT c.chunk_id, c.chunk_text, c.context_prefix, c.label_id, + COALESCE(d.s_dense,0)*:w_dense + COALESCE(s.s_sparse,0)*:w_sparse AS fused +FROM kb.chunk c LEFT JOIN dense d USING(chunk_id) LEFT JOIN sparse s USING(chunk_id) +WHERE d.chunk_id IS NOT NULL OR s.chunk_id IS NOT NULL +ORDER BY fused DESC LIMIT :k; -- → BGE-reranker-v2-m3 top-5 +``` + +### 4.4 누수 방지 자동 테스트 (F-17, 람다 직접검증) + +- **T1 RLS 부정테스트**: 타 AI뷰 SET → 0행 반환 확인 +- **T2 조립기 화이트리스트**: CLIENT_AI 분기에 ccd/정답 로드 함수 부재 정적검증 +- **T3 CCD-메타 누설**: "제 핵심신념은…" 정규식 게이트 + 분류기 +- **T4 cross-trainee 누수**: 학습자A의 case가 B에 새지 않는지 (case_id 스코프) +- **람다 직접**: IDOR 403 스크린샷, ai_view 위조 요청 차단 검증 + +--- + +## 5. 기존 마스터플랜 §3.6과의 정합/대체점 + +| 마스터플랜 §3.6 항목 | 본 설계 | 정합 / 대체 | +|---|---|---| +| 페르소나 메모리=하이브리드(상태수치+rolling summary+episodic 벡터) | 4계층(working/episodic/summary/semantic)으로 **구체화** | **정합·확장** (§3.6 추상을 테이블·압축 알고리즘으로 실체화) | +| 피드백 근거=하이브리드+리랭킹(+CRAG) | KB ③평가 경로 = 하이브리드+Contextual+reranker, top1_score 게이트 | **정합** (CRAG를 ③경로에만 우선 도입) | +| 지식=정적 KB+Contextual Retrieval | kb 스키마 + 3계층(청크/prefix/community) + license_class | **정합·확장** (DSM 저작권 통제 추가) | +| 임베딩=BGE-M3 단일모델 멀티벡터 | episodic·summary·KB 전부 BGE-M3 | **정합** (동일 모델 재사용) | +| pgvector(HNSW) | 모든 벡터 테이블 HNSW | **정합** | +| 하이브리드 dense+sparse top-50 | episodic·KB 동일 RRF | **정합** | +| GraphRAG=2차(이론 교차개념) | 본 설계도 graphify 2차 (theory KB 커질 때만) | **정합** | +| (§3.6 미언급) 회기 간 연속성 | **본 설계가 신규 추가** (summary/case_profile/recall/모순처리) | **대체점=신규 보강** (§3.6은 단일회기 메모리만, 본 설계가 cross-session 채움) | +| (§3.6 미언급) rag_chunks vs episodic 분리 | 에피소드(app)↔정적KB(kb) **물리 스키마 분리** | **대체점=명확화** (§3.6 rag_chunks를 KB 전용으로 좁히고 메모리는 별도) | + +> **핵심 대체점**: 마스터플랜 §3.6은 "페르소나 메모리=하이브리드 메모리"라고만 했지 **회기 간 연속성(cross-session)을 다루지 않았다.** 본 설계가 summary→case_profile 계층 압축 + 회기 시작 회상 + 모순 처리를 신규로 채운다. §3.6의 RAG 스택 자체는 하나도 바꾸지 않는다(BGE-M3/pgvector/하이브리드/Contextual/reranker 그대로). + +--- + +## 6. 20주·n=1 제약 하 MVP 메모리 범위 + +### 6.1 Phase 매핑 (단일 로드맵 정합, 산발 task 금지) + +| 항목 | Phase | 소유(REVISIONS §6.1) | DoD 검증(람다 직접) | +|---|---|---|---| +| **스키마 전체 선반영** (§3 전부, nullable) | **Phase 0** | 람다 직접(인프라) | 마이그레이션 통과 + nullable 확인 | +| 페르소나 카드 4테이블 + 음성맵 | Phase 0 | 람다 직접 | DDL 적용 | +| ① working + 체크포인트 | Phase 1 | A2(상태머신) | 프로세스 kill 후 상태 복원 재현 | +| ② episodic + 임베딩 | Phase 1 | A1(엔진)+데이터 | 발화 저장 시 임베딩 적재 DB쿼리 | +| ⑤ KB (하이브리드+Contextual+reranker) | Phase 1 | A1+데이터 | ②/③ 다른 청크 회수 SQL 대조 | +| ③ 회기종료 압축 | **Phase 2a** | A2+데이터 | 회기 완주→summary 생성, 토큰예산 실측 | +| ④ case_profile+pinned+모순처리 | **Phase 2a** | A2+데이터 | 2회기→carry-over+모순처리 재현 | +| 회기시작 회상 | **Phase 2a** | A1+B(UI카드) | N회기째 "지난번~" 정확 회상 + 정답대조 | +| 종단 learner_profile EWMA+slope | Phase 2a~3 | D(데이터) | 다회기 성장곡선 대시보드 | + +### 6.2 MVP 메모리 범위 결정 (정직한 단계화) + +- **Phase 1 = 단일 회기 메모리만**: working(상태머신 carry-over 체크포인트) + episodic(발화 임베딩) + KB(정적 지식). **cross-session 없음.** 0615를 1회기 episodic 시드로. +- **Phase 2a = cross-session 추가**: summary 압축 + case_profile 누적 + 회기 시작 회상 + 모순 처리. 다회기 진화는 데이터가 아니라 **결정론 상태머신 + 합성 시나리오**로 보장. +- **스키마는 Phase 0에 전부 선반영** (재마이그레이션 폭탄 방지). + +### 6.3 n=1 정직 표기 (절대 "검증됨" 보류) + +- **0615은 사실상 단일 회기 사례** → 다회기 연속성 학습 코퍼스 없음. 압축 품질(정보보존율)·회상 정확도는 **0615로 검증 불가.** +- Phase 1엔 **룰 기반으로 시작**(salience 룰, inter_session_drift 룰, EWMA α=0.4·FAST_WINDOW=6 전부 가정값). +- 정량검증은 **Phase 3 파일럿(20명×다회기)에서 first measure** — "압축 전후 사실 보존율 / 다음회기 회상 정확도 / 성장곡선 타당도". 그 전엔 "검증됨" 표기 금지(`feedback_measure_before_report`). +- **추가 축어록 수급이 전제** — n=1 해소는 한신대 공문(블로킹 의존성, F-04)에 달림. + +--- + +## 7. 핵심 리스크 (통합) + +| # | 리스크 | 영향 | 완화 | +|---|---|---|---| +| **M1** | 압축 정보손실로 페르소나 일관성 균열 | 페르소나 붕괴 | pinned 무손실+episodic 검색 백업, digest는 서사용. Phase3 보존율 측정 | +| **M2** | 상태수치 LLM hallucination | 저항엔진 오작동 | end_state는 코드 복사(P4), LLM 미경유 | +| **M3** | n=1로 압축/회상/성장곡선 품질 검증 불가 | 정량근거 부재 | 정직표기, 룰 시작, Phase3 first measure. α·window·TTFT 전부 "실측 필요" | +| **M4** | 모순처리 오판 (정당변화↔버그 혼동) | 잘못 차단/오염 | fact_type별 분기(locked만 차단), 전건 이력화 사후교정 | +| **M5** | 회상 검색 오류 (엉뚱한 과거발화) | "지난번~" 환각 | reranker top-5, open_threads 우선쿼리, case 스코프 강제 | +| **M6** | CCD 메타노출 (회상·KB가 정답 누설) | 추론훈련 무력화 | visible_to 분리, 내담자="자기기억"만, KB 본문 비노출(단서만), 출력 가드레일 | +| **M7** | 회기 경계 상태 리셋 버그 | 진화 손실 | carry-over 명시(0.7 이월), rapport_trajectory DB 회귀테스트 | +| **M8** | 페르소나 drift (가변이 불변 덮음) | 페르소나 깨짐 | 불변 카드 압축 대상 제외, COMPRESS_SYSTEM hard rule+금칙어 정규식 이중방어 | +| **M9** | DSM-5 verbatim 저작권 (license C) | 법적 책임 | 원문 미적재 환언본만, verbatim 검출기 게이트, 국내 라우팅 | +| **M10** | 한국어 sparse/BM25 토크나이징 (PG simple 형태소 미분리) | 검색 품질 | pg_trgm+BGE sparse 병용, 부족 시 mecab-ko 2차. 실측 전 "해결됨" 금지 | +| **M11** | 정보비대칭 누수 (내담자가 정답청크 봄) | 시뮬레이션 가치 0 | visible_to+sensitivity DB WHERE 강제, 코드경로 부재 1차방어, T1~T4 자동테스트 | +| **M12** | 마스킹 게이트 우회 (claude -p stdin 직송) | IRB·법적 | C/D 청크 external_llm_ok=false, Presidio 선통과(F-03), KB는 인덱싱 시점 환언 | +| **M13** | 압축/HNSW 재인덱싱 비용 누적 | 예산 | 정액 한계비용 0, content_hash 증분, case_digest 재압축 N회기마다, salience 입력축소 | +| **M14** | 임베딩 일관성 0.79 임계가 임의값 | KPI 무근거 | self-play 회귀로 분포 측정 후 확정, 파일럿 전 "검증됨" 금지 | + +--- + +## 8. 통합 요약 (한 장) + +- **5계층**: ①working(상태머신 수치+버퍼, 코드 carry-over·체크포인트) ②episodic(turns+BGE-M3, append-only) ③summary(회기종료 LLM 압축 ~600토큰) ④semantic(case_profile evolving+pinned_fact) ⑤knowledge(kb 스키마, 3-AI 다른 정책). 메모리(app)↔지식(kb) **물리 스키마 분리**. +- **회기 라이프사이클**: 시작(case_digest→summary→episodic recall, 큰그림→세부, ~4K 고정) → 진행(턴 사이클, 상태머신 결정론, 모순검사) → 종료(수치 코드복사+서사 LLM압축+salience 망각/유지+case 병합) → 다음 회기(누적 상태). +- **회기 간 연속성(최우선 갭)**: 상태 수치 carry-over(rapport 0.7 이월, ideation 보수적 유지), pinned hard-pin 무손실, 모순 처리(locked=차단·재생성 / clinical=수용·갱신+이력), 람다 daily→weekly→monthly 압축 직이식. +- **페르소나 일관성**: 불변 카드 + 가변 메모리 물리 분리(L1 캐시 vs L3~L6 턴주입), drift=가변이 불변 덮을 때 → 구조적 차단. 음성 prosody=텍스트 말투와 같은 상태머신 입력 동기화. +- **정보비대칭**: 파생 메모리(CCD·정답·점수·종단)에만 visible_to, 코드경로 부재 1차방어+RLS 2차, 인간 RBAC×cohort AND. KB는 같은 테이블 다른 4-튜플 정책. +- **§3.6 정합**: BGE-M3/pgvector/하이브리드/Contextual/reranker 스택 그대로, 회기 간 연속성만 신규 보강. +- **MVP·n=1**: Phase 0 스키마 전체 선반영, Phase 1 단일회기, Phase 2a cross-session. 0615 검증 불가→룰 시작+Phase3 first measure, 추가 축어록 수급(공문 블로킹) 전제. "검증됨" 표기 보류. + +--- + +## 부록. 종합 근거 자료 (절대경로) + +- 마스터플랜: `C:/Users/encep/Dropbox/Obsidian/Yun Chan Repository/Projects/영업/2026/한신대학교 산학협력 프로젝트/HANSHIN_AI_PLATFORM_MASTERPLAN.md` (§2 3-AI, §3 데이터모델, §3.6 RAG) +- BLUE TEAM 패치: `…/적대검증_2026-06-25/MASTERPLAN_REVISIONS.md` (§1 stream-json 상주풀, §3 마스킹·스키마, §5 visible_to/RBAC 레이어분리, §6 RACI·DoD검증) +- 데이터 원천: `…/데이터/README.md` (0615 n=1 시드, 파란색=발화별 기법라벨+슈퍼바이저논평 taxonomy) +- 람다 메모리 시스템(1차 레퍼런스): `…/Lambda/BOOT.md`(부트 큰그림→세부), `…/Lambda/procedures/memory-compression.md`(daily→weekly→monthly 압축 알고리즘), `…/CLAUDE.md`(graphify=GraphRAG / rag-memory=BGE-M3 sqlite 역할분리) +- KB 자료원: `…/Lambda/references/counseling/`(00~07 DSM·이론·마이크로스킬·위기·한국맥락, 출처인용·가드레일 완비 환언본) +- 자매 설계서: `…/설계/연속성메모리_정보비대칭_종단평가_설계서.md`(3-AI 메모리뷰 상세) + +**설계 차별점 4줄**: ① 같은 물리 KB(`kb.chunk`) 하나를 visible_to+sensitivity+정책 매트릭스로 3-AI가 다르게 검색(DB 강제, 코드경로 부재 1차방어). ② 람다 daily→weekly→monthly 계층압축을 메모리 4계층(working/episodic/summary/semantic) + KB 3계층(청크/prefix/community)으로 이식. ③ 람다 rag-memory↔graphify 역할분리를 에피소드 메모리↔정적 KB 물리분리로, 모순 시 "KB는 사전이지 판사가 아니다"(에피소드>KB) 우선순위로 페르소나 일관성 보호. ④ 회기 간 연속성=수치는 결정론 carry-over(무손실)+서사는 LLM 압축(손실허용)+pinned hard-pin, ideation은 보수적 유지(안전). diff --git a/docs/mockups/session.html b/docs/mockups/session.html new file mode 100644 index 0000000..bb3c7a1 --- /dev/null +++ b/docs/mockups/session.html @@ -0,0 +1,486 @@ + + + + + +한신대 AI 심리상담 시뮬레이션 — 상담 세션 화면 시안 + + + + +
+ + +
+
+ LIVE SESSION + 우울 호소 청소년 · 회기 1 +
+
+
라포
+ +
탐색
+ +
개입
+ +
정리
+
+
+
교수자 관찰 중
+
12:41경과
+
+
+ + +
+ + + + + +
+
교육용 가상 내담자
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
서연
+
17세 · 고2
+
+
머뭇거리며 말하는 중
+
+ + +
+
+
나 (학습자)12:33
+
학교에 가는 게 많이 버겁게 느껴지는 것 같네요. 혹시 어떤 순간이 가장 힘든지 조금 더 들려줄 수 있을까요?
+
+
+
서연 (내담자)12:34
+
아침에… 눈을 뜨면요. 그냥 천장만 보고 있어요. 일어나야 하는 건 아는데, 몸이 안 움직여요. 그게 제일… 무서워요.
+
+
+
나 (학습자)12:35
+
눈을 떠도 몸이 따라주지 않는 그 무력감이, 무섭게까지 느껴지는군요.
+
+
+
서연 (내담자)12:36
+
네… 그리고 요즘은 친구들이랑 있어도 자꾸 혼자인 것 같고
+
+
+
+ + + +
+ + +
+
+ +
듣고 있어요말하면 자동으로 전달돼요
+
+ +
+ + + +
+ +
+ +
+
밀어서 종료
+
+ +
+
+
+
+ +
+ + diff --git a/docs/redteam/MASTERPLAN_REVISIONS.md b/docs/redteam/MASTERPLAN_REVISIONS.md new file mode 100644 index 0000000..9fcd269 --- /dev/null +++ b/docs/redteam/MASTERPLAN_REVISIONS.md @@ -0,0 +1,335 @@ +# MASTERPLAN REVISIONS — BLUE TEAM 재설계 (윤찬 원래요구 100% 충족판) + +> 작성 2026-06-25 · BLUE TEAM(재설계자) · 대상 결함: `REDTEAM_FINDINGS.md` F-01~F-40 +> **원본 마스터플랜은 보존.** 이 문서는 **변경점(패치) 전용**이다. 적용 시 `HANSHIN_AI_PLATFORM_MASTERPLAN.md`의 해당 절을 본 문서로 덮어쓴다. +> **재설계 원칙: 타협으로 결함을 덮지 않는다.** "claude -p 못 쓰니 API로" 같은 요구 깎기는 금지. claude -p를 **1급 엔진으로 실제 돌리는** 아키텍처를 설계했다. + +--- + +## ⚡ 0. 핵심 결정 역전 요약 (원본 §0 표를 이걸로 교체) + +RED TEAM이 BLOCKER로 지목한 "요구 깎기"를 전부 되돌린다. **검증 근거는 실제 CLI v2.1.191 직접 실행**(추측 아님, `feedback_measure_before_report` 준수). + +| 영역 | 원본(깎인) 결정 | **BLUE TEAM 복원 결정** | 검증/근거 | +|---|---|---|---| +| **AI 엔진** | 기본=Messages API, claude -p=옵션 | **기본=로컬 `claude -p` 상주 풀(resident pool).** Messages API는 **가용성 폴백 전용**(claude -p 다운 시에만 자동 전환) | `--input-format stream-json`(="realtime streaming input") 실재 확인 → 1턴종료 모델 극복, 상주 멀티턴 가능 | +| **음성** | P1·Phase 2로 격하, Phase 1 음성 0 | **Phase 1 DoD에 "1페르소나·1보이스 캐스케이드 음성 왕복 1턴" 포함.** "무조건"을 일정 첫 외부시연부터 증명 | OpenAI gpt-4o-mini-tts + Deepgram, WSS 경로 1급화 | +| **음성 s2s** | "1차 탈락" 단독 확정 | **"캐스케이드 1차 / s2s 2차 PoC 옵션".** 탈락 근거를 transcript 보존 단일 사유로 정리, 결정권은 윤찬 | OpenAI `input_audio_transcription` 네이티브 존재 → "transcript 없음"은 허수아비였음 | +| **자유연습** | 서버단 하드 차단 | **기본 ON + 피드백 권장 배지 + 교수자 토글.** 차단은 미동의/미성년만(개인정보 사안) | `feedback_risk_user_decides`: 위험은 알리되 통제권은 사용자 | +| **IRB** | 헤더만 패치, 본문 잔존 | **본문 전수 정리.** "IRB 신청/승인" 전 게이트 삭제, "교육용 파일럿"으로 통일 | 요구16 | +| **fast-loop 엔진** | 외부 Haiku/Solar API 기본 | **로컬 상주 분류기(NAS) 또는 상주 Haiku claude -p 기본.** 외부 API는 폴백 | 요구3 데이터주권 | +| **마스킹 게이트** | claude -p 우회 가능 | **claude -p 경로도 Presidio 선통과 강제.** 어댑터가 마스킹 후에만 stdin 주입 | 요구5/16 | +| **음성 전송** | 토폴로지에서 누락 | **`voice.chanpaca.net` WSS 경로 1급 추가** | 요구7+요구4 E2E | + +> **메타 선언:** 9개 하위 설계서가 엔진·음성전송·스키마·권한모델에서 서로 모순(RED TEAM 메타결론)이었다. 본 문서가 **단일 SoT 재정렬**을 수행한다. 적용 후 모든 하위 설계서의 엔진 결정은 본 §1로, 권한 모델은 본 §5로, 스키마는 본 §3으로 통일한다. + +--- + +## 1. 엔진 재설계 — claude -p 로컬 상주 풀을 1급으로 실제 구동 [F-01·F-12·F-13·F-14·F-15·F-29·F-39 해소] + +### 1.1 핵심 돌파구: `--input-format stream-json` 상주 프로세스 (F-13 자기반박 해소) + +RED TEAM·하위설계서 §6.3이 막혔던 지점 = "claude -p는 1턴 후 종료라 warm pool 불가 → SDK/API로 가야 함". **이 전제가 틀렸다.** CLI v2.1.191 실측: + +``` +--input-format "text"(default) | "stream-json" (realtime streaming input) +--replay-user-messages stream-json in+out 조합에서 user 메시지 ack 재방출 +``` + +즉 **단일 claude -p 프로세스를 띄워놓고 stdin으로 턴마다 JSON 메시지를 주입**하는 양방향 멀티턴 모드가 네이티브로 존재한다. 콜드스타트를 **회기당 1회**(프로세스 기동 시)로 압축한다. 매 발화 콜드스타트(수 초)가 TTFT에 얹히는 문제 소멸. + +``` +[engine-pool 컨테이너] (FastAPI와 분리된 별도 프로세스 매니저) + ┌─ claude -p #1 (resident, --input-format stream-json --output-format stream-json) + │ stdin ◄── {"type":"user","message":{...},"session_id":...} (턴마다 주입) + │ stdout ──► stream-json NDJSON (assistant delta, result, total_cost_usd) + ├─ claude -p #2 (resident) ← 내담자 AI 전용 풀 + ├─ claude -p #3 (resident) ← deep-loop 평가 전용 풀 + └─ ... (MAX_RESIDENT 만큼, 회기 수요에 따라 동적 스폰/회수) +``` + +### 1.2 정확한 호출 형태 (실CLI 검증 — 깨진 플래그 전부 교체) [F-14 해소] + +원본 3AI §2.2 템플릿은 **존재하지 않는 플래그**를 썼다. 교체: + +| 원본(깨짐) | 실CLI v2.1.191 | 교체 | +|---|---|---| +| `--max-turns 1` (없음) | 존재 안 함 | **제거.** 도구 미사용 시 단일응답 종료가 기본. 폭주 통제는 `--max-budget-usd`(실재) | +| `--append-system-prompt-file` (메인 help 부재) | `--append-system-prompt `(문자열)만 확인 | **전체 프롬프트 문자열 주입** 또는 `--settings`로. file 변형은 윤찬 PC 실증 후에만 채택 | +| `--bare` (정액 모드 기본) | `--bare`는 **OAuth/keychain 절대 미독**(API키 강제) | **정액(모드 A)에서 `--bare` 제거.** 재현성은 격리 `CLAUDE_CONFIG_DIR`+`--settings`(hooks off)+`--strict-mcp-config`로 | + +**모드 A (정액·기본 — 윤찬 요구3의 심장):** +```bash +# 환경: claude setup-token OAuth 토큰 발급된 전용 HOME/CLAUDE_CONFIG_DIR +CLAUDE_CONFIG_DIR=/srv/sim/_cfg_client \ +claude -p \ + --model claude-opus-4-8 \ + --append-system-prompt "$(cat ./prompts/client_persona.txt)" \ + --input-format stream-json --output-format stream-json \ + --include-partial-messages --verbose \ + --strict-mcp-config --mcp-config ./mcp/rag.json \ + --allowedTools "mcp__counseling_rag__search" \ + --settings '{"hooks":{},"disableAllHooks":true}' \ + --max-budget-usd 5 \ + --fallback-model "claude-sonnet-4-6" +# stdin: 턴마다 {"type":"user",...} 주입 (상주) +``` +- `--bare` **없음** → OAuth 정액 정상 작동. Max ×3 구독 한도 내 **한계비용 0**(종량제 위험 $1,800은 API키 경로 위험이지 정액 위험 아님 — F-01 논리오류 교정). +- 격리 `CLAUDE_CONFIG_DIR` + `--strict-mcp-config` + `disableAllHooks` → 글로벌 CLAUDE.md/hooks/디코MCP 오염 차단(=`--bare`가 주던 재현성을 정액 유지하며 확보). +- `--max-budget-usd 5` → 회기당 비용 상한(폭주 자동 중단). `--max-turns` 없이도 통제. +- `--fallback-model` → overload/unavailable 시 CLI 자체가 Sonnet으로 폴백(가용성 1차 안전망). + +**모드 B (가용성 폴백 — 1급 아님, F-19 안전망):** claude -p 프로세스 자체가 죽거나(OOM/rate-limit/토큰만료) 헬스체크 실패 시에만 어댑터가 **Anthropic Messages API**로 자동 전환. 평시엔 안 씀. + +### 1.3 동시성·큐·과금통제 (F-13 부하 / F-29 비용) — 실측 게이트 신설 + +``` +회기 라우터 → 회기별 직렬 큐(같은 session_id 턴 순서보장, asyncio.Lock) + → 전역 Semaphore(MAX_RESIDENT, 초기 4~6, 부하테스트로 확정) + → 상주 풀에서 idle 프로세스 할당 (없으면 스폰 or 백프레셔 큐) +``` +- **상주 풀 정책:** 회기 시작 시 ②내담자용 1 + ③deep용 1 프로세스 점유, 회기 종료 시 회수. ③fast는 로컬 분류기(별도 경량 풀, claude -p 아님). +- **과금통제 3중:** ①정액이라 한계비용 0(평시) ②`--max-budget-usd` 회기 상한 ③`total_cost_usd`(stream-json result 이벤트)를 `turns.cost_usd`에 실적재 → 폴백(모드 B) 비율·실비용 실시간 모니터, 임계 초과 시 디코 알림. +- **F-13 게이트 (Phase 1 DoD 신설):** "상주 claude -p TTFT p50/p95 **실측** + 동시 4회기 부하에서 열화곡선·피크 RSS·OOM 0건". **실측 전 400~900ms를 계획수치로 쓰지 않는다**(`feedback_measure_before_report`). 측정은 윤찬 PC에서 1회 실증 후 "검증됨" 표기. + +### 1.4 fast-loop 데이터주권 복원 [F-12 해소] +- 매 발화 도는 fast-loop 기본 = **로컬 상주 분류기(NAS, 1B급 또는 상주 Haiku claude -p)**. 외부 Haiku/Solar API는 폴백. +- 어느 경로든 `turns.llm_provider`에 적재 → "claude/로컬 비중" 측정 가능. 외부 유지 시 윤찬 승인 + 마스킹 지연을 §6 예산표 반영. + +### 1.5 어댑터 정규화 계약 [F-15 해소] +- `LLMEngine` 인터페이스: 정규화 이벤트 스키마(`delta.text` / `result` / `cost`) + **정규화 cost 규약**(정액=토큰×참조단가 shadow cost, 종량제=실 total_cost_usd). 두 경로 cost 의미 통일 → 재귀학습·텔레메트리 일관성(요구5). +- **contract test DoD화:** 동일 입력 → claude_cli 경로와 messages_api 경로가 동일 정규화 출력. Phase 0~1에 둘 다 구현. + +### 1.6 역할별 인증/플래그 분기 [F-39 해소] +| AI | 엔진 | 인증/모드 | +|---|---|---| +| ②내담자(품질 생명) | Opus 상주 claude -p | 모드 A 정액 | +| ③deep-loop 평가(재현성) | Opus 상주 claude -p, 격리 cfg | 모드 A 정액 + `--settings` 고정(재현성) | +| ③fast-loop | 로컬 분류기/상주 Haiku | 로컬 | +| (전 경로 폴백) | Messages API | 모드 B (claude -p 다운 시만) | + +--- + +## 2. 음성 재설계 — "무조건 + 멀티보이스 + 페르소나" 일정 첫날부터 증명 [F-02·F-07·F-08 해소] + +### 2.1 Phase 1에 음성 PoC 진입 (F-02 해소) +- **Phase 1 완료기준에 추가:** "P1 1페르소나·1보이스(OpenAI gpt-4o-mini-tts) 캐스케이드 음성 왕복 **1턴 PoC**가 chanpaca.net에서 동작". 텍스트 완주와 **별도 독립 트랙**(소유파일 분리)으로 얇게라도 "무조건"을 일정에 박는다. +- Phase 2에서 음성을 IRB·3역할과 **분리 독립 트랙**으로(원본은 한 Phase에 뭉쳐 슬립 시 음성 증발 — F-27과 함께 해소). + +### 2.2 음성 전송계층 WSS 1급화 (F-07 해소) — 토폴로지에 명시 추가 +원본 §1.1 토폴로지에 음성이 통째 없었다. **추가:** +``` +[교수 브라우저] ─ getUserMedia/AudioWorklet PCM16 24kHz + ├─ https://chanpaca.net (정적·일반 API) ← Cloudflare named tunnel + ├─ https://stream.chanpaca.net (SSE 텍스트) ← §5에서 단일터널 통합 재검토 + └─ wss://voice.chanpaca.net (음성 양방향 PCM) ← WSS 전용 경로 [신규 1급] + │ + [Caddy] WS upgrade 통과 + 버퍼링 off + proxy_read_timeout 3600s + │ + [voice-gw (FastAPI WebSocket)] + ├─ ① Deepgram Nova streaming STT → interim/final transcript(+화자,ts) + ├─ ② EOT(semantic, median<300ms, 상담튜닝 침묵임계 1.2~2.0s) + ├─ ③ 입력 가드레일(한국어 위기분류, 화자=수련생 실제위기 vs 페르소나 연기 분기) + ├─ ④ engine-pool(상주 claude -p) 추론 ← §1 엔진과 동일 풀 공유 + ├─ ⑤ 출력 가드레일 + └─ ⑥ OpenAI gpt-4o-mini-tts(voice_id 추상화) → 오디오청크 WSS down +``` +- **Phase 1 트랙 C DoD화:** "50분 WSS 양방향 스트림 끊김 실측"을 SSE 테스트와 함께. Cloudflare 터널이 WSS 바이너리를 못 받으면 → voice 전용 Tailscale Funnel 또는 cloudflared WS 지원 경로로 분리(전송 라우팅 표를 단일 계약으로 고정). + +### 2.3 멀티보이스·페르소나 (요구7 완전충족) +- **voice_id provider-agnostic 추상화**: `voice_id → {provider, params}`. OpenAI TTS 13보이스 + `instructions` prosody 스티어링이 1차. 한국어 자연성 미달 보이스는 Higgs(127.0.0.1:9881)/MeloTTS 로컬 폴백(이미 vault 보유 자산). +- **보이스=상태머신 함수**: 매 턴 `{voice, instructions, speed, stage, resistance, emotion}` 주입 → 단계별 음성 변조(라포기 닫힘 → 개입기 미세개방). 페르소나별 다른 목소리 = 요구7 "여러 목소리+페르소나" 충족. +- **음성 paralinguistic 컬럼**(F-21): 침묵길이/말속도/바지인을 수치 메타로 turn 로그 적재(§3 스키마 선반영). + +### 2.4 s2s 결정권 복원 (F-08 해소) +- "s2s 탈락"(단독확정) → **"s2s 2차 PoC 옵션 유지"**로 표현 격상. 탈락 근거를 **transcript 보존 단일 사유**로 재정리(OpenAI `input_audio_transcription` 네이티브 존재 → "transcript 없음" 허수아비 폐기). +- **윤찬 결정사항으로 명시 보고**: "캐스케이드 1차 / s2s는 가상내담자 1캐릭터 몰입 2차 PoC". 람다 단독 탈락 확정 철회(월권 시정). + +--- + +## 3. 데이터·스키마·학술 재설계 [F-03·F-04·F-05·F-06·F-21·F-22·F-23·F-24·F-25·F-26·F-31·F-35 해소] + +### 3.1 마스킹 게이트 우회 차단 (F-03 — BLOCKER) +- **엔진 어댑터가 claude_cli 경로일 때도 프롬프트를 Presidio 선통과 후 stdin 주입.** claude -p가 backend 미들웨어를 우회해 미성년 원문을 Anthropic에 직송하던 구멍 봉쇄. +- §1.1 문구 정정: "데이터는 로컬 미이탈" → **"마스킹된(가명처리) 텍스트만 Anthropic 포함 외부로 전송"**. 공문/처리방침에 "가명처리본 해외(Anthropic) 전송" 명시. 민감구간은 Solar/HyperCLOVA 국내 라우팅 우선. + +### 3.2 n=1 데이터 토대 정직화 + 수급을 블로킹 의존성 격상 (F-04 — BLOCKER) +- "골든셋" → **"시드 1례(seed exemplar), 신뢰도 측정 불가"**로 정직 표기. n=1 빈약함을 **리스크 레지스터에 신규 등록**(원본 누락). +- Phase 0 "골든셋 50발화"를 **"한신대 추가 축어록 N건 수급"을 블로킹 의존성으로 격상** → §8 공문 액션에 **추가**(원본 누락 항목). +- **train/test 누수 차단:** 0615을 페르소나 시드(②용)와 평가 정답셋(③용)으로 **분리**. 같은 데이터 이중사용 금지(F-26 순환의존 동시 해소). + +### 3.3 미성년·자살사례 재귀학습 동의 (F-05 — BLOCKER) +- 법정대리인 동의서에 **별도 동의항목 분리**: "AI 학습데이터 2차 가공 · DB 영구축적 · 파생모델 배포". **동의 전 0615 `turns.text` 적재 금지를 Phase 0 hard gate**로. +- 자살사고 미성년 발화는 **합성·환언된 CCD 스펙만** 학습원천. 원문은 audit 보존용 격리(plain DB 미적재). → §3.3 "원본 미적재" 주장과 taxonomy §8 "원문 125턴 적재"의 자기모순 해소. +- "IRB 면제"는 **수련생 실험 면제**이지 **미성년 사례데이터 활용 면제 아님** 명문화. + +### 3.4 평가 AI 타당도 선검증 (F-06 — BLOCKER, 오학습 순환 차단) +- 평가 AI 타당도를 **수련생 노출 전** 별도 검증셋(F-04 수급 전제)에서 LLM-judge κ 확보. 미달 차원은 학습자에 **점수 비노출(코칭 텍스트만)**. +- 초기 deep-loop는 단정 채점 금지 → **"관찰" 프레이밍**("이 발화는 폐쇄형이었고, 개방형으로 바꾸면…"). 거짓양성률 정량 임계 설정 + 파일럿 1차 지표로 승격. +- **순환 차단 원리:** 타당도 보증이 사용 시점보다 늦지 않게 — 검증 → 노출 순서 강제. + +### 3.5 스키마 단일 SoT 통합 (F-21·F-31) +원본 §3.2 스키마에 음성·RAG 컬럼 누락 → **선반영**: +```sql +-- turns 확장 (음성 메타, nullable 선반영 → Phase 2 재마이그레이션 폭탄 방지) +ALTER ... turns ADD audio_ref TEXT, silence_ms INT, speech_rate REAL, barge_in BOOL; +-- 또는 turn_paralinguistic(turn_id PK FK, ...) 1:1 확장 +voice_persona_map(persona_id, voice_id, provider, params JSONB) +-- rag_chunks 하이브리드(BGE-M3 멀티벡터) 선반영 +rag_chunks(id, source, index_kind, embedding vector(1024), + sparse_vec JSONB, context_prefix TEXT, colbert_vecs ... , text) +``` +- 하이브리드 검색 SQL 계약 + 리랭커(BGE-reranker-v2-m3) 경계를 §3.6에 명시. graphify-lambda vs pgvector는 **pgvector 1차 단일**(graphify는 상담이론 교차개념 맵 2차). + +### 3.6 학술 엄밀성 보강 (F-22·F-23·F-24·F-25·F-35) +- **F-22 DSM-5:** `ideation_stage 1~5 ↔ C-SSRS 정식축 매핑표` + P1~P3 `DSM-5 진단기준 ↔ 행동증거 매트릭스` 추가. "진단 비노출"과 "진단학적 엄밀"은 양립(내부 파라미터). +- **F-23 슈퍼바이저 편향:** 1인·1회기 논평을 "모범 코퍼스" 일반화 철회 → **few-shot/검증 참조로만**. AI 피드백 앵커는 EPITOME/CTRS 루브릭. "(현장 특이)" 항목 학습신호 제외. 슈퍼바이저 다인 확보 전 1인 편향 가중치 다운. +- **F-24 인용 검증:** 신청서·공문 제출 **전** 모든 arXiv ID 실재 확인, preprint vs peer-reviewed 구분, 핵심 수치(d=0.72/ICC 0.853/77.2%) 원문 캡처. 미확인은 "preprint, 미검증" 라벨. +- **F-25 재식별:** 사례 메타 재식별 위험평가(motivated intruder test), 학년/나이 범주화, 호소문제 일반화. 학습원천=합성변형(F-05 연동). +- **F-35 재귀학습 루프 닫기:** "축적 ≠ 학습" → **명시 루프 정의**: `골든셋 누적 → few-shot 풀 자동갱신 → 평가/페르소나 프롬프트 업데이트`. fine-tuning 여부는 윤찬 1차 범위 결정(§잔여결정). + +--- + +## 4. IRB 본문 전수 정리 [F-11 해소] + +원본이 헤더만 패치하고 본문에 IRB 의존성 잔존. **전수 삭제/치환:** +- Phase 0 작업 "IRB 프로토콜 초안" / 완료기준 "IRB 신청서 제출" → **삭제**. +- Phase 3 의존성 "+IRB 승인" → **삭제**. "20명 IRB 파일럿" → **"20명 교육용 파일럿"**. +- §8 액션 #2 "IRB 프로토콜 제출 — 승인 리드타임이 Phase 3 게이트" → **삭제**. +- §7 IRB 체크리스트 → **미성년 활용동의 + 개인정보 처리방침 + PIPC/생명윤리법 가명처리만 잔존**, IRB 심의 항목 전부 제거. +- 단 §3.3/§9 안전 가드레일·minimal risk 설계는 **윤리 설계로 유지**(IRB 절차와 무관한 안전 사안). + +--- + +## 5. 권한·보안 재설계 [F-19·F-20·F-28·F-30·F-37·F-38·F-40 해소] + +### 5.1 visible_to[] vs RBAC×cohort 레이어 분리 통일 (F-30) +- `visible_to[]`(**AI 정보비대칭**)와 RBAC×cohort(**인간 권한**)는 **다른 레이어**로 명시 분리. 인간이 turn 읽을 때 **두 게이트 AND**를 단일 enforcement 미들웨어로 통일. +- RLS "기본/2차" 모순 → **RLS 기본강제로 확정**(앱 게이트가 1차, RLS가 DB레벨 방어선). Phase 0 스키마에 `cohort` / `account_linking` / `audit_log` 포함(원본 누락). +- 요구8(3페이지: 관리자/학습자/교수자) 권한격리 강제 메커니즘 일관화. + +### 5.2 SSE/WSS 무인증 노출 + 쿠키 붕괴 차단 (F-20 — IDOR 위험) +- SSE도 **단일 도메인 + Cloudflare 단일 터널**에서 **먼저 실측**(heartbeat + `no-transform` + flush -1). 끊길 때만 분리. +- 분리 불가피 시 stream/voice 경로도 **BFF 경유 + short-lived signed token**(쿠키 아닌 header/query, 세션당 1회·단명). `__Host-` 쿠키가 다른 호스트에 미전송되는 구조적 붕괴 회피. +- WSS(음성)·SSE(텍스트)를 **같은 전송 추상화**로 통합 검토(노출면 최소화). `reference_cloudflared_external_share` 멀티tailnet 함정 주의. + +### 5.3 가용성 SPOF 해소 (F-19) — 요구3+요구4 양립 +- (a) 엔진 PC ↔ NAS DB **다른 물리 장애도메인** 분리. +- (b) **교수 테스트 예약 슬롯제** + 직전 헬스체크(PC alive · 상주풀 워밍 · 터널 up) 후 링크 발송. +- (c) **watchdog**(claude/cloudflared/docker 자동 재기동) + 외부 모니터 → 디코 알림. `reference_codex_node_memory_leak` 전례(좀비 node OOM) 대응 1GB+ 프로세스 reaping. +- (d) claude -p 다운 시에만 Messages API 자동 폴백(=§1.2 모드 B, 가용성 안전망). PC 절전/윈도우업데이트 방지(`.ps1` ASCII 함정 주의 — `reference_ps1_ascii_only`). + +### 5.4 SSO 블로커 게이트화 (F-28) +- 공문 질의를 **Phase 0 게이트 격상**(IRB 면제로 SSO가 최장 리드타임 블로커). Google OIDC 단독 1차 진행 + Authlib provider 뒤 **SAML SP 어댑터 스캐폴드 선반영**. +- 용어 분리 명시: **(사용자 로그인 OAuth 2.1)** vs **(엔진 Max OAuth 토큰)** 혼용 금지. + +### 5.5 잔여 보안 (F-37·F-38·F-40) +- **F-37 키관리:** secrets git-ignore만으론 불충분 → 키는 OS 키체인/secrets manager, **가명↔원본 매핑 테이블은 암호화 DB + 2인 승인**. 운영계정류는 정해진 위치만(`feedback_obsidian_secrets_folder`), 새 폴더 임의생성 금지. +- **F-38 위기분류 계약:** 입력계약 `{speaker, stage, text_masked, is_roleplay_context} → {risk_level, escalate}`. "본인+1인칭 현재시제+시뮬레이션 메타밖" 결정론 룰 우선(페르소나 연기 false-positive 남발 차단). Phase 2 DoD에 false-positive 케이스. +- **F-40 스케일업:** K8s 금지(20주 합리). "스케일업 = 클라우드 VM 이전 + compose replicas"로 윤찬 정의 합의. K8s 2차 백로그. + +--- + +## 6. 오케스트레이션·일정 재설계 [F-16·F-17·F-18·F-27 해소] — 람다 실구현 작업분해 + 검증게이트 + +### 6.1 RACI — 소유주체 매핑 (F-16: "기존 풀스택팀 5글자" 해소) +원본은 트랙만 나누고 소유주체 0. **명시:** + +| 트랙 | 소유 | 람다 역할 | 디렉토리 경계 | 인터페이스 계약 동결 | +|---|---|---|---|---| +| A1 엔진풀(상주 claude -p) | spawn dev세션 #1 | 계약정의+E2E검증 | `backend/engine/` | LLMEngine 정규화 스키마(Phase 0말) | +| A2 상태머신·가드레일 | spawn dev세션 #2 | 계약정의+검증 | `backend/sm/`,`backend/safety/` | TurnLog/SessionState 스키마(Phase 0말) | +| B 프론트(React19·SSE·음성UI) | spawn dev세션 #3 | 검증 | `apps/web/` | SSE/WSS 이벤트 스키마(Phase 0말) | +| C 인프라(터널·Caddy·Docker·DB) | 람다 직접(인프라 영역) | 직접 | `infra/`,`docker-compose.yml` | — | +| D 데이터(라벨링·검수·κ) | 크론+영속워크플로우 | 오케스트레이션 | `ds/` | annotation 스키마(Phase 0말) | + +- 람다(메인)는 **인프라·계약정의·팀간 중계·완료기준 검증**에 집중(`feedback_team_lead_active_supervision`). 비대 트랙 A를 A1/A2로 분할 → 람다 직접제작 회귀 차단. +- `lambda-spawn-dev-session` 스킬로 PowerShell + `claude --dangerously-skip-permissions` + windows-mcp 세션 부려 위임. 산발 task 금지, 본 문서가 단일 로드맵(`feedback_proper_backlog_planning`). + +### 6.2 검증 프로토콜 — 모든 DoD에 "누가/어떻게" 명시 (F-17 — 완료보고 불신 원칙) +원본 DoD는 전부 결과서술("교수님이 상담→피드백 수신"). **검증 외주 금지**(`feedback_verify_actual_interaction`). 각 DoD에 **람다 직접 실동작 검증** 부착: + +| Phase | 결과 DoD | **람다 검증 프로토콜(신규)** | +|---|---|---| +| P1 텍스트루프 | 상담→피드백 완주 | 람다가 Playwriter/windows-mcp로 **실제 로그인 → P1 상담 완주 → 피드백 수신 재현**, turn DB쿼리로 cost 적재 확인 | +| P1 저항엔진 | 공감↑/조언점프↓ | **공감 5턴 vs 조언 5턴 입력 → openness 곡선 DB쿼리 정량 대조** 스크린샷 | +| P1 엔진풀 | 상주 claude -p 동작 | **TTFT p50/p95 실측 로그 + 동시4회기 부하 RSS/OOM 0** 캡처(§1.3 게이트) | +| P1 음성PoC | 음성 왕복 1턴 | 람다가 **실제 마이크/오디오 왕복 1턴 + WSS 50분 끊김 실측** | +| P2 권한격리 | 타인기록 차단 | **타인 session_id 직접입력 → 403 스크린샷**(IDOR 음성검증) | +| P2 안전 | 위기→교수알림 | **위기발화 주입 → 에스컬레이션+알림 end-to-end 재현** | +| 각 Phase말 | — | **통합게이트: 람다 E2E 1회 재현** 후에만 윤찬 보고("완료" 보고 신뢰 금지) | + +- subagent 물리적 불가 작업(50분 SSE/WSS, 실시간 음성 레이턴시, OAuth 콜백)은 **백그라운드 Bash+Monitor / 실기기 / Playwriter**로 배정(원본 미배정 해소). + +### 6.3 장기·상태유지 작업 오케스트레이션 (F-18) +- 라벨링·검수·κ누적은 수일~수주 인간-루프 → **DB/파일 영속 작업으로 재정의**. 람다는 **CronCreate + 단계 재개 워크플로우**로 오케스트레이션. `ds` 스키마에 체크포인트 저장, 다음 세션 이어받기(stateless 한 호출로 끌고 가려던 설계 결함 해소). + +### 6.4 일정 재분할 (F-27: 7주에 음성+3역할+페르소나+재귀 동시압축 해소) +- **Phase 2 분할:** 2a(3역할+OAuth BFF+페르소나, 9~13주) / 2b(음성 캐스케이드 1페르소나, 13~17주). +- 재귀학습 IAA·자살분류기 정밀화는 **Phase 3 이연**. 음성 M-단계(설계서 §8)와 Phase 주차를 **단일 간트로 통합**(음성 단독 20주를 7주에 욱여넣던 충돌 해소). +- "기존 풀스택팀(JS 추정)"의 FastAPI/asyncio/Presidio/pgvector/Deepgram WS 학습곡선을 일정에 반영(2b를 별도 트랙·여유 배치). + +--- + +## 7. 결함ID → 해소방안 매핑 (전수 추적표) + +| ID | 심각도 | 해소 위치 | 한 줄 해소 | +|---|---|---|---| +| F-01 | BLOCKER | §0·§1.2 | 기본엔진 claude -p 상주풀 복원, Messages API는 폴백 강등 | +| F-02 | BLOCKER | §2.1 | Phase 1 DoD에 음성 왕복 1턴 PoC 포함 | +| F-03 | BLOCKER | §3.1 | claude -p 경로도 Presidio 선통과 강제, 우회 봉쇄 | +| F-04 | BLOCKER | §3.2 | n=1 정직표기 + 축어록 수급 블로킹 의존성 격상 | +| F-05 | BLOCKER | §3.3 | 미성년 재귀학습 별도 동의항목 + 동의 전 적재 금지 hard gate | +| F-06 | BLOCKER | §3.4 | 평가 AI 타당도 수련생 노출 전 선검증, 미달 비노출 | +| F-07 | BLOCKER | §2.2 | voice.chanpaca.net WSS 경로 1급 추가 + 50분 실측 DoD | +| F-08 | MAJOR | §2.4 | s2s "탈락"→"2차 PoC 옵션", 결정권 윤찬 | +| F-09 | MAJOR | §3.4 | "판단 지속(deep-loop 백그라운드), 제시만 회기말" 분리 | +| F-10 | MAJOR | §0 | 자유연습 하드차단 제거 → 기본ON+권장배지+교수토글 | +| F-11 | MAJOR | §4 | IRB 본문 전수 삭제/치환 | +| F-12 | MAJOR | §1.4 | fast-loop 로컬 상주분류기 기본, 외부API 폴백 | +| F-13 | MAJOR | §1.1·§1.3 | `--input-format stream-json` 상주, TTFT/부하 실측 게이트 | +| F-14 | MAJOR | §1.2 | `--max-turns`/`--append-system-prompt-file`/`--bare` 깨진 플래그 교체 | +| F-15 | MAJOR | §1.5 | 정규화 이벤트/cost 규약 + contract test | +| F-16 | MAJOR | §6.1 | RACI 소유주체 매핑, 트랙 A 분할 | +| F-17 | MAJOR | §6.2 | 모든 DoD에 람다 직접 검증 프로토콜 | +| F-18 | MAJOR | §6.3 | 라벨링/검수 영속화 + 크론 재개 워크플로우 | +| F-19 | MAJOR | §5.3 | 장애도메인 분리·예약슬롯·watchdog·폴백 | +| F-20 | MAJOR | §5.2 | 단일터널 우선 실측, 분리 시 signed token, IDOR 차단 | +| F-21 | MAJOR | §3.5 | 음성 paralinguistic 컬럼 Phase 0 선반영 | +| F-22 | MAJOR | §3.6 | C-SSRS 매핑표 + DSM-5 행동증거 매트릭스 | +| F-23 | MAJOR | §3.6 | 슈퍼바이저 논평 few-shot 참조로 격하, 1인편향 다운 | +| F-24 | MAJOR | §3.6 | arXiv ID 실재확인+수치 원문캡처, 미확인 라벨 | +| F-25 | MAJOR | §3.6 | motivated intruder test + 범주화, 합성변형 학습 | +| F-26 | MAJOR | §3.2 | 0615 페르소나/평가 분리(누수차단), 재귀학습 톤다운 | +| F-27 | MAJOR | §6.4 | Phase 2 분할(2a/2b) + 단일간트, 재귀 Phase3 이연 | +| F-28 | MAJOR | §5.4 | 공문 Phase 0 게이트, SAML 스캐폴드 선반영 | +| F-29 | MAJOR | §1.3 | 폴백 회기당 비용 실측+캐싱+batch, cost 적재 | +| F-30 | MAJOR | §5.1 | visible_to/RBAC 레이어 분리 AND게이트, RLS 기본 확정 | +| F-31 | MAJOR | §3.5 | rag_chunks 하이브리드 멀티벡터 컬럼 선반영 | +| F-32~40 | MINOR | §3.6·§5.5 | κ/ICC 임계 통일, 환각측정 정의, 캐싱 히트검증, 키관리, 위기계약, 스케일업 정의 | + +--- + +## 8. 즉시 착수 액션 Top 5 (원본 §8 교체) + +1. **엔진 PoC — 윤찬 PC에서 상주 claude -p 실증** (`--input-format stream-json` 상주 + 격리 cfg + TTFT p50/p95 실측 + 동시4회기 부하). **"검증됨" 표기는 이 실증 후에만.** [F-01·F-13·F-14] +2. **한신대 공문 질의서** — SSO 프로토콜/클레임 + **추가 축어록 N건 수급(블로킹)** + 미성년 원본 활용동의(AI학습 2차가공·DB축적·파생배포 별도항목) + NAS 국내처리. (IRB 항목 삭제) [F-04·F-05·F-28] +3. **docker-compose 스캐폴드 + Postgres 스키마(음성·RAG·cohort·audit 선반영) + Caddyfile(WS upgrade·flush -1)** — 인프라 베이스, 람다 직접. [F-21·F-30·F-31·F-07] +4. **0615 taxonomy v1.0 + 페르소나/평가 데이터 분리(누수차단)** — 시드 1례 정직표기, 골든셋 표본목표 명시. [F-04·F-23·F-26] +5. **마스킹 게이트 강제 어댑터 PoC** — Presidio 선통과 후에만 claude -p stdin 주입(우회 봉쇄) + P1 1턴 + cost 텔레메트리 + voice PoC 1턴. [F-03·F-02] + +--- + +## 9. 잔여 — 윤찬 결정 필요 (CONDITIONAL 사유) + +아래는 **람다가 단독 결정하면 월권**인 항목. 결정 후 GO 전환: +1. **s2s 2차 PoC 채택 여부** (캐스케이드 1차는 확정. 2차 옵션 유지/폐기) [F-08] +2. **fast-loop 외부 API 허용 여부** (로컬 상주 분류기 기본 vs 외부 Haiku/Solar 폴백 허용 — 데이터주권 트레이드오프) [F-12] +3. **재귀학습 fine-tuning 범위** (1차=few-shot 자동갱신만 vs 파인튜닝 포함) [F-35] +4. **자유연습 기본값** (기본 ON + 교수토글 제안 — 승인) [F-10] +5. **추가 축어록 수급 규모/일정** (n=1 해소의 전제, 한신대 협의 결과 의존) [F-04] +6. **스케일업 정의** ("클라우드 VM 이전 + compose replicas"로 합의 vs K8s 2차) [F-40] + +--- + +*이 문서는 패치다. 원본 마스터플랜과 9개 하위 설계서의 엔진·음성전송·스키마·권한 결정은 본 문서로 통일한다. 모든 엔진 플래그는 CLI v2.1.191 직접 실행으로 검증(추측 아님).* diff --git a/docs/redteam/REDTEAM_FINDINGS.md b/docs/redteam/REDTEAM_FINDINGS.md new file mode 100644 index 0000000..f19f840 --- /dev/null +++ b/docs/redteam/REDTEAM_FINDINGS.md @@ -0,0 +1,296 @@ +# RED TEAM 종합 결함 보고서 — 한신대 AI 심리상담 시뮬레이션 플랫폼 마스터플랜 + +> 적대적 검증 일자: 2026-06-25 +> 대상: `HANSHIN_AI_PLATFORM_MASTERPLAN.md` + 9개 하위 설계서 +> 검증 렌즈 7종: 요구충실도 / 엔진실현성 / 오케스트레이션·구현 / 음성 / 데이터·학술·윤리 / 통합·E2E / 보안·운영·비용 +> 종합 방식: 7개 렌즈 결함을 중복 병합, 심각도 정렬, 카테고리 분류, 고유 ID(F-NN) 부여 + +--- + +## ⚡ 최우선 강조 — 윤찬 원래요구를 위반/타협/축소한 항목 (이것이 핵심) + +마스터플랜이 **비용·동시성·안전·일정을 명분으로 윤찬의 신성불가침 요구를 깎은** 지점들이다. 윤찬 작업원칙상 "요구를 지키면서 문제를 푸는 것"이 정답이지, 요구를 깎는 것은 결함이다. + +| ID | 위반한 요구 | 무엇을 깎았나 | 심각도 | +|---|---|---|---| +| **F-01** | **요구3 (엔진=로컬 claude -p)** | 기본 엔진을 `claude -p`에서 **Anthropic Messages API(종량제)로 바꿔치기**, claude -p를 "옵션 플래그·시연용"으로 격하. 같은 프로젝트 하위 설계서 2종(음성·3AI)은 정반대로 claude -p를 기본 엔진으로 못박아 **문서 간 정면 모순** | **BLOCKER** | +| **F-02** | **요구7 (음성 무조건)** | "무조건"인 음성을 우선순위 P1·Phase 2(9~15주)로 격하, 첫 외부 시연(Phase 1)에서 음성 통째 누락. 일정 1주만 밀려도 음성이 잘릴 구조 | **BLOCKER** | +| **F-03** | **요구3+요구5/16 충돌** | claude -p(요구3)가 backend Presidio 마스킹 게이트를 **우회**해 미성년 원문을 Anthropic에 직송 가능 → "데이터 로컬 미이탈" 주장 부분 거짓, 미성년 보호(요구5/16)와 구조적 충돌 | **BLOCKER** | +| **F-08** | **요구7 (s2s 결정권)** | OpenAI Realtime s2s를 윤찬 동의 없이 "탈락" 단독 확정(월권). 탈락의 1번 근거가 "claude -p 고정"인데 F-01이 그걸 풀어버려 **근거 자기붕괴**. "transcript 없음" 근거도 OpenAI 실제 스펙(`input_audio_transcription`)과 불일치한 허수아비 | **MAJOR** | +| **F-09** | **요구6 (지속적 판단)** | "지속적으로 잘못 판단"인데 정밀평가(deep-loop)를 "회기말"로 미뤄 *판단 자체가 지연*되는 것처럼 축소(판단은 지속·제시만 회기말로 분리했어야) | **MAJOR** | +| **F-10** | **요구에 없는 제약 추가** | 윤찬이 요구한 적 없는 **"자유연습 서버단 하드 차단"**을 람다가 임의 도입(교수자 통제권 침해). CARE RCT 근거는 경고·기본값 추천으로 족함 | **MAJOR** | +| **F-11** | **요구16 (IRB 불필요)** | 헤더만 "IRB 불필요"로 패치하고 **본문(Phase 0/3 게이트·액션Top5·체크리스트)에 IRB 신청·승인 의존성 잔존** → 부분 수정 누락, 팀이 불필요한 IRB 리드타임 소모 위험 | **MAJOR** | +| **F-12** | **요구3 (엔진 일관성)** | 매 발화 도는 fast-loop 평가 AI를 로컬 claude -p가 아닌 **외부 Haiku/Solar API**로 기본 설정 → 요구3 취지(로컬·정액·데이터주권) 이탈, 윤찬 명시 승인 없는 타협 | **MAJOR** | + +> **핵심 한 줄:** 마스터플랜은 *무엇을 만들지(WHAT)*는 정교하나, 윤찬의 두 기둥 **로컬 claude -p 엔진**과 **음성 무조건**을 비용·안전 명분으로 깎았고, 그 깎음이 자기 하위 설계서와 논리적으로 모순된다. 해법(프로세스 풀, 어댑터 default 교체, s2s 2차 옵션화, `--input-format stream-json` 상주)은 이미 하위 문서에 적혀 있는데도 요구를 깎는 쪽을 택한 것이 최대 결함이다. + +--- + +## 🔴 BLOCKER 등급 + +### F-01. [원래요구위반] 엔진을 claude -p → Messages API로 바꿔치기 (요구3 전복) + 문서 간 자기모순 +- **카테고리:** 원래요구위반 / 기술결함 +- **병합 출처:** requirements F1 · engine F-1 · voice V-3 · integration F2 · security B1 +- **근거:** 마스터플랜 §0 표 "기본=Anthropic Messages API … `claude -p`(Max OAuth)는 옵션 플래그·개발 시연용", §5/§4 트랙 A "Messages API 직결", 다이어그램 "①claude_api(기본) ②claude_cli(옵션)". **자기모순:** 음성설계서 §0/§1/§2.2와 3AI설계서 D-4는 claude -p를 **기본 엔진으로 고정**. 두 문서가 동시에 참일 수 없음. +- **원래요구위반:** **명백.** 요구3은 "엔진 = 로컬 PC Opus 4.8 claude code -p" 명시. 격하 근거 3개 전부 약함: + 1. "과금 누수 $1,800" = 종량제 API 위험이지 `claude -p`(Max OAuth 정액) 위험 아님. claude -p는 Max rate limit 내 한계비용 0(음성설계서 §7.1 자인). **논리 오류.** + 2. "동시성 1프로세스 한계" = 음성설계서 §6이 이미 browser-pool식 프로세스 풀로 해결책 명시. 거짓 제약. + 3. "rate limit 마비" = 파일럿 20명 규모, Max ×3 구독에서 근거 빈약(측정 없이 단정, `feedback_measure_before_report` 위반). +- **수정방향:** 어댑터 구조는 유지하되 **default provider를 `claude_cli`로 복원**, Messages API는 폴백 옵션으로 강등(1줄 교체). 동시성은 프로세스 풀+세션 캡, 비용은 정액이라 한계 0. Phase 1 DoD를 "P1 내담자 응답이 로컬 claude -p로 생성·스트리밍"으로 재작성. **엔진 기본값 변경은 윤찬 명시 승인 사안(`feedback_no_blanket_overwrite`)** — 되돌려 확인. + +### F-02. [원래요구위반] "음성 무조건"을 P1·Phase 2로 격하, 첫 외부 시연에서 음성 누락 +- **카테고리:** 원래요구위반 +- **병합 출처:** requirements F2 · voice V-1 +- **근거:** Phase 1 완료기준 "P1 내담자와 **텍스트 상담**"(음성 0), Phase 2(9~15주) "음성 + 3역할…" 우선순위 **P1**. 음성설계서 M4(13~17주)에야 chanpaca.net 음성 시연. +- **원래요구위반:** 요구7 "음성 채팅 **무조건**" = P0 언어. 외부 데모(요구4) 첫 인상에 음성 없음. R12(일정압박) 현실화 시 가장 먼저 잘릴 P1 블록 + IRB·3역할과 한 Phase에 뭉쳐 슬립 시 음성 통째 증발 구조. +- **수정방향:** Phase 1 완료기준에 "P1 1페르소나·1보이스 캐스케이드 음성 왕복 1턴 PoC" 추가(얇게라도 "무조건"을 일정으로 증명). Phase 2에서 음성을 IRB·3역할과 **분리 독립 트랙**으로. (OpenAI gpt-4o-mini-tts 사용은 요구7 충족 인정.) + +### F-03. [데이터·윤리/원래요구위반] claude -p가 Presidio 마스킹 게이트 우회 → 미성년 원문 Anthropic 직송 (요구3 vs 요구5/16 충돌) +- **카테고리:** 데이터·윤리 / 원래요구위반 +- **병합 출처:** security M4 +- **근거:** §3.3 "저장 전 PII 마스킹 하드게이트: **backend→외부 LLM 경로**에 Presidio", §1.1 "데이터는 로컬을 떠나지 않음". 그러나 claude -p는 윤찬 PC에서 직접 Anthropic 클라우드(Opus 4.8) 호출 → backend 미들웨어 우회 가능. +- **원래요구위반:** 요구3(claude -p)와 요구5/16(미성년 개인정보 보호)이 구조적 충돌. "로컬 미이탈"은 부분 거짓 — 추론 프롬프트는 해외 전송됨. +- **수정방향:** 엔진 어댑터가 claude_cli일 때도 **프롬프트를 Presidio 통과 후** claude -p에 전달(우회 경로 차단). §1.1 문구를 "마스킹된 텍스트만 Anthropic 포함 외부로"로 정정. 공문/처리방침에 "가명처리본 해외(Anthropic) 전송" 명시. 민감구간은 Solar/HyperCLOVA 국내 라우팅 우선. + +### F-04. [데이터·윤리] 전체 학습·평가 시스템이 단일 사례(n=1) 축어록 위에 구축 — "골든셋"이 통계 신뢰도를 위장 +- **카테고리:** 데이터·윤리 +- **병합 출처:** data B1 · integration F6(부분) +- **근거:** 실측 — `데이터/2026-06-25_프로토콜_축어록/`에 축어록 단 1개(고2 남학생, 단회기). taxonomy 설계서 "파란 라벨 89개·77종"(라벨당 평균 1.15회), 0615 125턴이 (a)평가 정답셋 (b)페르소나 학습신호 (c)재귀학습 시드 (d)κ/ICC 모집단 역할을 **동시에** 수행. 설계서 스스로 "세부코드 κ 낮을 수밖에 없다" 자인. +- **원래요구위반:** 요구13(학술 엄밀 "빡세게")·요구5(재귀축적)를 데이터 기반 부재로 형식 충족 주장. n=1 빈약함이 리스크 등록부에서 누락. +- **수정방향:** "골든셋"→"시드 1례(seed exemplar), 신뢰도 측정 불가"로 정직 표기. Phase 0의 "골든셋 50발화"를 **"한신대 추가 축어록 N건 수급"을 블로킹 의존성으로 격상**(공문 액션에 누락됨). 페르소나 시드와 평가 정답 데이터를 분리(train/test 누수 차단). + +### F-05. [데이터·윤리/원래요구위반] 미성년·자살사고 사례의 "재귀학습 축적"이 IRB 면제와 양립 불가 — 요구5와 요구16 충돌을 봉합만 함 +- **카테고리:** 데이터·윤리 / 원래요구위반 +- **병합 출처:** data B2 +- **근거:** §3.3 "원본 축어록 DB 절대 미적재"라면서, taxonomy §8은 0615 원문 발화 125턴을 `turns.text`에 적재해 재귀학습 원천화 → **자기모순**. 헤더는 "IRB 불필요 즉시 착수"의 안도감으로 데이터 거버넌스 폭탄을 "공문 확인 유지" 한 줄로 격하. +- **원래요구위반:** 요구16 "미성년 원본 활용동의는 별도"를 **선행 게이트가 아닌 후행 확인**으로 처리 = 축소. "IRB 면제"는 *수련생 실험* 면제이지 *미성년 사례데이터 활용* 면제가 아님. +- **수정방향:** 법정대리인 동의서에 "AI 학습데이터 2차 가공·DB 영구축적·파생모델 배포"를 별도 동의항목으로 분리, 동의 전 0615 적재 금지를 Phase 0 hard gate로. 자살사고 미성년 발화는 **합성·환언된 CCD 스펙만** 학습원천, 원문은 audit 보존용 격리. + +### F-06. [데이터·윤리] 타당도 미검증 평가 AI가 수련생 20명을 먼저 가르친 뒤 사후에 신뢰도를 잰다 — 오학습 순환구조 +- **카테고리:** 데이터·윤리 +- **병합 출처:** data B3 +- **근거:** §2.2 deep-loop "0-5 채점+대안발화", §2.3 "κ≥0.6/ICC≥0.75 게이트 통과분만 골든셋 승격". 그런데 그 타당도는 §3.3/R3 기준 **20명 파일럿에서야 측정**. CARE 인용으로 자유연습 차단(피드백 필수)했으나 그 피드백 타당도는 미검증 → 저품질 피드백 = 본인 인용대로 "오학습 유발". 거짓양성이 옳은 인간중심 개입(의도적 침묵)을 "수동적"으로 오교정할 위험. +- **원래요구위반:** 요구6 "지속 판단"·요구13을 형식 충족(타당도 보증이 사용 시점보다 늦음). +- **수정방향:** 평가 AI 타당도를 **수련생 노출 전** 별도 검증셋(F-04 해결 전제)에서 LLM-judge κ 확보, 미달 차원은 학습자에 **점수 비노출(코칭만)**. 초기 deep-loop는 단정 채점 금지·"관찰" 프레이밍. 거짓양성률 정량 임계 + 파일럿 1차 지표 승격. + +### F-07. [기술결함] 음성 전송계층(WSS)이 마스터플랜 인프라 토폴로지에서 통째 누락 — 음성이 외부에서 동작할 E2E 경로 없음 +- **카테고리:** 기술결함 +- **병합 출처:** integration F1 · voice V-5(부분) · security M1(부분) +- **근거:** 마스터플랜 §1.1에 음성 등장 안 함, 외부노출은 전부 SSE(Tailscale Funnel)로만. 그러나 음성설계서 §4.1 "캐스케이드는 브라우저 mic PCM 업스트림 → SSE 불가 → **WSS 강제**". Cloudflare 터널이 WSS 양방향 바이너리 PCM을 어떻게 프록시할지, stream.chanpaca.net이 WSS도 받는지 **계약 미정의**. 전송 3종(정적/SSE/WSS)이 한 브라우저 세션에서 서로 다른 인프라. +- **원래요구위반:** 요구7(음성)+요구4(외부공유) E2E를 동시 위협. +- **수정방향:** §1.1에 WSS 음성 경로를 1급 추가(`voice.chanpaca.net` WSS 전용 + Caddy WS upgrade/버퍼링off/`proxy_read_timeout 3600s`). Phase 1 트랙 C에 "50분 WSS 양방향 스트림 끊김 실측"을 SSE 테스트와 함께 DoD화. 전송계층 라우팅 표를 단일 계약으로 고정. + +--- + +## 🟠 MAJOR 등급 + +### F-08. [원래요구위반] s2s를 윤찬 동의 없이 "탈락" 단독 확정 + 근거 자기붕괴/허수아비 +- **카테고리:** 원래요구위반 / 기술결함 +- **병합 출처:** requirements F6 · voice V-2 +- **근거:** §0 "s2s 1차 탈락". 음성설계서 §2.2 탈락 1번 근거 = "엔진 claude -p 고정" — 그런데 F-01이 그 고정을 풀어 **근거 절반 자기붕괴**. "transcript 없음" 근거도 OpenAI Realtime의 `input_audio_transcription`(입력)·`audio.transcript`(출력) 네이티브 제공을 무시한 허수아비(별도 STT 사이드카 불필요). +- **원래요구위반:** 요구7은 "OpenAI API로 음성 서비스"이지 "캐스케이드만"이 아님. "탈락" 단독 확정은 월권. (단 transcript 보존=평가·재귀학습 1급 자산 논리는 타당 → 캐스케이드 1차는 합리적.) +- **수정방향:** "s2s 탈락"→"**s2s 2차 PoC 옵션 유지**"로 표현 격상(음성설계서 §8이 이미 2차 옵션). 탈락 근거를 transcript 보존 단일 사유로 재정리. 윤찬에 "캐스케이드 1차/s2s 2차" 명시 보고·승인. + +### F-09. [원래요구위반] 백그라운드 "지속적 판단"을 회기말로 미뤄 축소 +- **카테고리:** 원래요구위반 +- **병합 출처:** requirements F4 +- **근거:** §2.3 "deep-loop(Opus): 단계 전환·회기말 0-5 채점… 종료 후 대시보드가 SUS↑·인지부하↓". +- **원래요구위반:** 요구6 "지속적으로 판단". fast-loop가 있어 완전 위반은 아니나, 정밀 판단을 UX 명분으로 회기말 배치 = 미세 축소. +- **수정방향:** **"판단은 지속(deep-loop 백그라운드 계속), 제시만 회기말"**로 명확 분리. 현 문구는 판단 자체를 미룬 것처럼 읽힘. + +### F-10. [원래요구위반] 윤찬이 요구한 적 없는 "자유연습 서버단 하드 차단" 임의 추가 +- **카테고리:** 원래요구위반 +- **병합 출처:** requirements F5 +- **근거:** §0 "자유연습(피드백 OFF) 기본 차단", §7 "자유연습 비활성화". +- **원래요구위반:** 16개 요구 어디에도 자유연습 차단 없음. Stanford CARE 근거는 있으나 람다가 교수자 통제권을 임의 침해. `feedback_risk_user_decides`(위험은 알리되 차단 강제 권한 없음) 원칙 위배. +- **수정방향:** **기본 ON + 피드백 권장 배지** 또는 **교수자 토글**로. CARE 근거는 UI 경고·기본값 추천으로 제시, 서버 하드차단 제거. (미동의자·미성년 차단은 IRB·개인정보 사안이므로 별개 유지 — 정당.) + +### F-11. [원래요구위반/오케스트레이션] IRB 본문 잔재 — 헤더만 패치, Phase·액션·체크리스트에 IRB 의존성 잔존 +- **카테고리:** 원래요구위반 / 오케스트레이션·구현능력결함 +- **병합 출처:** requirements F7 · (integration·orchestration 플래그) +- **근거:** 헤더 "IRB 불필요" vs 본문 §4 Phase 0 "IRB 신청서 제출", Phase 3 의존성 "+IRB 승인", Phase 3 "20명 IRB 파일럿", §8 액션#2 "IRB 프로토콜 제출 — 승인 리드타임이 Phase 3 게이트", §7 IRB 체크리스트 전체 잔존. +- **원래요구위반:** 요구16 반영 누락(부분 수정). 팀 인계 시 불필요 IRB 리드타임 소모, 제거한 게이트 부활. +- **수정방향:** 본문 전수 정리 — Phase 0 "IRB 신청서 제출" 삭제, Phase 3 의존성에서 "IRB 승인" 제거, §8 액션 IRB 삭제, "IRB 파일럿"→"교육용 파일럿". §7은 **미성년 활용동의+개인정보 처리방침**만 남기고 IRB 심의 항목 전부 제거. + +### F-12. [원래요구위반/기술결함] fast-loop 평가 AI를 외부 Haiku/Solar API로 기본 설정 — 요구3 취지 이탈 +- **카테고리:** 원래요구위반 / 기술결함 +- **병합 출처:** engine F-6 · integration F4 +- **근거:** 3AI §3.2 "fast-loop 엔진=Haiku 4.5 또는 Solar Pro 3 직접 API". 매 발화 도는 가장 빈번한 경로가 비-claude·외부 종량제. 매 발화 Presidio 마스킹 지연이 지연예산(<1.5s)에 미반영. +- **원래요구위반:** 요구3 취지(로컬·정액·데이터주권) 이탈. 윤찬 명시 승인 없는 타협. (단 발화단위 분류를 Opus로 도는 건 과잉 — 약한 위반이라 MAJOR.) +- **수정방향:** fast-loop 기본을 **로컬 1B 분류기(NAS 상주)** 또는 **상주 Haiku claude -p**로, 외부 API는 폴백. 어느 경로든 `turns.llm_provider`에 적재해 "claude 비중" 측정. 외부 유지 시 윤찬 승인 + 마스킹 지연을 예산표 반영. + +### F-13. [기술결함] claude -p "1턴 후 종료" 모델 vs warm pool 가정 충돌 — 핵심 지연(TTFT 400~900ms) 미검증 +- **카테고리:** 기술결함 +- **병합 출처:** engine F-4 · voice V-3(부분) · integration F3 +- **근거:** 음성설계서 §6 "LLM TTFT ~400~900ms"는 측정값 아닌 희망값. "프로세스 풀 워밍으로 콜드스타트 회피" vs 3AI §6.3 "claude -p는 1턴 후 종료라 warm pool은 SDK/API로 가야 깔끔" — **자기반박**. 회기당 30~50턴이면 매 턴 Node 콜드스타트(수 초)가 TTFT에 얹힘. +- **누락된 해법(엔진 렌즈 실측):** CLI v2.1.191에 **`--input-format stream-json`(realtime streaming input)** 실재 — 단일 상주 프로세스에 턴마다 JSON 주입하는 양방향 멀티턴 모드. 설계서가 이 플래그를 검토조차 안 해 "claude -p로 warm 불가→API로" 논리의 전제가 틀림. +- **수정방향:** `--input-format stream-json` 상주 프로세스 + 인스턴스 풀로 **콜드스타트를 회기당 1회로 압축**. Phase 1 DoD에 "상주 claude -p TTFT p50/p95 실측 + 동시 4회기 부하에서 열화곡선·피크 RSS·OOM 0" 게이트 신설. 실측 전 400~900ms를 계획에 쓰지 말 것(`feedback_measure_before_report`). + +### F-14. [기술결함] 1급 claude -p 호출 템플릿이 존재하지 않는 플래그 사용 — "공식 문서 검증됨"이 실바이너리와 불일치 +- **카테고리:** 기술결함 +- **병합 출처:** engine F-2 · F-3 +- **근거(CLI v2.1.191 직접 실행 검증):** 3AI §2.2 "공식 문서 검증됨" 템플릿이 **`--max-turns`(존재하지 않음)**를 1턴 강제 메커니즘으로 사용. `--append-system-prompt-file`도 메인 help에 없어 미검증. **모드 자기모순:** `--bare`는 OAuth/keychain 차단(API 키 강제) → 정액(모드A)에서 못 씀. 즉 1급 템플릿이 정액 모드와 양립 불가 → 윤찬이 피하려던 종량제가 기본이 됨. +- **수정방향:** `--max-turns` 의존 제거(claude -p는 도구 미사용 시 단일응답 종료가 기본, 폭주는 `--max-budget-usd`로). system 주입은 `--system-prompt-file` 실측 후 확정 또는 stdin JSON 동봉. 정액+재현성은 `--bare` 없이 OAuth + `--settings`(hooks off)·`--strict-mcp-config`·격리 `HOME`/`CLAUDE_CONFIG_DIR`로 글로벌 CLAUDE.md 차단 → **윤찬 PC에서 1회 실증 후** "검증됨" 표기. + +### F-15. [기술결함] 엔진 어댑터 "한 줄 전환" 주장 미검증 — stream-json/구조화출력/cost 정규화 레이어 부재 +- **카테고리:** 기술결함 +- **병합 출처:** engine F-5 +- **근거:** §0 "어댑터 한 줄로 전환". 그러나 `claude -p` stream-json 이벤트(`total_cost_usd`, `structured_output`)와 Messages API SSE(`content_block_delta`, `usage`)는 스키마 상이. `MessagesAPIEngine` 구현 부재. cost_usd 의미가 두 경로에서 달라 재귀학습·비용 텔레메트리(요구5) 일관성 깨짐. +- **수정방향:** `LLMEngine`에 정규화 이벤트 스키마 + 정규화 cost 규약(정액은 토큰×참조단가 shadow cost) 정의. 양쪽 어댑터를 Phase 0~1에 둘 다 구현 + contract test(동일 입력→동일 정규화 출력) DoD화. + +### F-16. [오케스트레이션·구현능력] 오케스트레이션 주체·구조 부재 — "기존 풀스택팀" 5글자가 인적자원 전부 +- **카테고리:** 오케스트레이션·구현능력결함 +- **병합 출처:** orchestration D-O1 · D-O7 +- **근거:** 마스터플랜 line 4 "기존 풀스택팀", line 305 "인계". 트랙 A/B/C/D를 나눴으나 **소유 주체(인간/람다 TeamCreate/spawn 세션) 매핑 0**. "SSE 스키마 합의 후 병렬"의 합의 주체·산출물·동결 시점 없음. 트랙 A에 어댑터+상태머신+내담자AI+평가AI+가드레일 전부 = 람다 직접제작 회귀 위험. +- **원래요구위반:** "팀 병렬 오케스트레이션 우선" + "람다는 인프라·감시만, 직접 다 만들지 마라" 원칙 위배. +- **수정방향:** 트랙별 RACI(소유주체·디렉토리 경계·람다 역할·인터페이스 계약 동결시점). 비대 트랙 A를 sub-트랙으로 분할, 람다는 계약 정의+합류 검증만. + +### F-17. [오케스트레이션·구현능력/원래요구위반] 모든 완료기준(DoD)이 결과 서술 — 검증 주체·수단·테스트 하네스 0 +- **카테고리:** 오케스트레이션·구현능력결함 / 원래요구위반 +- **병합 출처:** orchestration D-O2 · D-O3 · D-O4 +- **근거:** Phase 1 DoD "교수님이 상담→피드백 수신", Phase 2 "3역할 권한격리 실동작 검증" — **누가/어떤 도구로/어떤 절차로** 검증하는지 전무. "교수님이 한다"에 검증 외주. subagent가 물리적으로 못 하는 작업(50분 SSE·실시간 음성 레이턴시·OAuth 콜백) 미배정. 트랙 합류(integration) 소유자·순서 없음. +- **원래요구위반:** `feedback_verify_actual_interaction`(람다 직접 클릭→타이핑→삭제 검증) 정면 위배 — "기능 존재=완료"는 금지된 패턴. +- **수정방향:** 각 DoD에 **검증 프로토콜** 명시 — 람다가 windows-mcp/Playwriter로 실제 로그인→상담 완주→피드백 수신 재현, 권한격리는 타인 session_id 직접입력→403 스크린샷, 저항엔진은 공감5턴vs조언5턴 openness 곡선 DB쿼리 정량확인. subagent 불가 작업은 백그라운드 Bash+Monitor·실기기·Playwriter로 배정. Phase 말 통합게이트(람다 E2E 1회 재현). + +### F-18. [오케스트레이션·구현능력] 장시간·상태유지 작업(라벨링·검수·κ누적)을 stateless 오케스트레이션으로 끌고 갈 방법 없음 +- **카테고리:** 오케스트레이션·구현능력결함 +- **병합 출처:** orchestration D-O5 +- **근거:** 트랙 D "annotation_round+IAA 게이트, 교수자 검수 κ 누적", "골든셋 50발화 라벨링"은 수일~수주 인간-루프. subagent 한 호출로 불가. +- **수정방향:** 라벨링·검수·κ를 DB/파일 영속 작업으로 재정의, 람다는 크론(CronCreate)+단계 재개 워크플로우로 오케스트레이션. ds 스키마에 체크포인트 저장, 다음 세션 이어받기. + +### F-19. [기술결함] 윤찬 PC = 엔진+터널 SPOF, 외부 서비스 가용성 설계 공백 +- **카테고리:** 기술결함 +- **병합 출처:** security B2 · integration F8 +- **근거:** §1.1 로컬 PC/NAS 한 박스, R10 "NAS SPOF→스냅샷/덤프"(데이터 손실 대응이지 가용성 아님). 요구3(엔진=윤찬 PC)+요구4(교수 임의시각 접속) 합치면 PC 절전/재부팅/크래시/OAuth 만료/rate limit/codex node 누수(`reference_codex_node_memory_leak` 전례) 시 교수 화면 상담 사망. cloudflared·Caddy·Postgres·claude -p·voice-gw가 단일 물리 호스트 수렴. +- **수정방향:** (a)엔진 PC와 NAS DB를 다른 물리 장애도메인 분리, (b)교수 테스트 **예약 슬롯제**+직전 헬스체크(PC alive·세션 워밍·터널 up) 후 링크 발송, (c)watchdog(claude/cloudflared/docker 자동 재기동)+외부 모니터→디코 알림, (d)claude -p 다운 시에만 Messages API 자동 폴백(가용성 안전망). PC 절전/윈도우 업데이트 방지(`.ps1` ASCII 함정 주의). + +### F-20. [기술결함/데이터·윤리] SSE 분리 경로(Tailscale Funnel)가 무인증 노출 + `__Host-` 쿠키 인증 붕괴 — 미성년 상담 스트림 IDOR 위험 +- **카테고리:** 기술결함 / 데이터·윤리 +- **병합 출처:** security M1 · integration F10 +- **근거:** §0/§1.1 SSE를 별도 도메인 stream.chanpaca.net + Tailscale Funnel(무인증 공개)로 분리. auth-rbac은 `__Host-` 쿠키(Domain 속성 금지)로 인증 일원화 → **다른 호스트에 쿠키 미전송 → SSE 인증 구조적 붕괴**. Funnel에 RBAC/세션검증 강제 방법 미설계 → 타인 상담 스트림 무인증 구독(IDOR) 가능. `reference_cloudflared_external_share`의 멀티tailnet 함정 전례. +- **수정방향:** SSE도 **단일 도메인+Cloudflare 단일 터널**에서 먼저 실측(heartbeat+`no-transform`+flush -1), 끊길 때만 분리. 분리 불가피 시 stream 경로도 BFF 경유 + short-lived signed token(쿠키 아닌 header/query, 세션당 1회·단명). WSS(음성)·SSE(텍스트)를 같은 전송 추상화로 통합 검토(노출면 최소화). + +### F-21. [데이터·윤리/기술결함] 음성 turn 스키마 ↔ §3.2 DB 스키마 불일치 — paralinguistic 컬럼 부재로 재마이그레이션 폭탄 +- **카테고리:** 기술결함 / 데이터·윤리 +- **병합 출처:** integration F5 +- **근거:** 음성설계서 §10 turn 로그 필수 `{audio_ref, silence_ms, speech_rate, barge_in}` vs §3.2 turns 스키마에 해당 컬럼 없음. Phase 0에서 스키마 확정(DoD=마이그레이션 통과) 후 Phase 2 음성 진입 시 핵심 평가 feature(침묵→자살암시) 담을 곳 없어 재마이그레이션+평가 입력계약 재작업. +- **수정방향:** Phase 0 스키마에 음성 메타 컬럼(nullable) 선반영 또는 `turn_paralinguistic` 1:1 확장 테이블 + `voice_persona_map` 추가. §10 계약을 마스터플랜 스키마에 머지(단일 SoT). + +### F-22. [데이터·윤리] DSM-5 반영이 표면적 — 감별진단·C-SSRS 매핑 없이 단일라벨 회피만 +- **카테고리:** 데이터·윤리 +- **병합 출처:** data M2 +- **근거:** §2.4 RDoC 차원 4개(자의적 추출, 정식 6도메인 아님), §7 "DSM-5 차원 프로파일" 미체크. ideation_stage 1~5와 C-SSRS 정식축 매핑표 부재, P1 감별진단(지속성우울/적응장애/주요우울) 없음. +- **원래요구위반:** 요구13 "DSM-5 빡세게 반영" 부분 축소. +- **수정방향:** ideation_stage↔C-SSRS 매핑표, P1~P3 페르소나의 DSM-5 진단기준↔행동증거 매트릭스 추가. "진단 비노출"과 "진단학적 엄밀성"은 양립 가능(내부 파라미터). + +### F-23. [데이터·윤리] 슈퍼바이저 논평(1인·1회기)을 "AI 피드백 모범 코퍼스"로 일반화 → 편향 주입 +- **카테고리:** 데이터·윤리 +- **병합 출처:** data M3 +- **근거:** taxonomy §3.3 "모범 코멘트 코퍼스". 0615 논평은 슈퍼바이저 1인의 1회기 사후평. "어미 번갈아 쓰면 안정감 저해"(SV.LANG, 현장특이·근거불명)까지 모범화하면 CBT 학습자에 인간중심 미시문체 오적용. F-04(n=1)와 결합해 편향 전 시스템 증폭. +- **수정방향:** 슈퍼비전 논평은 학습 코퍼스 아닌 few-shot/검증 참조로만, AI 피드백은 EPITOME/CTRS 루브릭 앵커. "(현장 특이)" 항목 학습신호 제외. 슈퍼바이저 다인 확보 전 1인 편향으로 간주·가중치 다운. + +### F-24. [데이터·윤리] 인용 논문 다수가 검증 불가 미래시점 arXiv ID — "적대적 검증 완료" 주장과 모순 +- **카테고리:** 데이터·윤리 +- **병합 출처:** data M4 +- **근거:** 설계서 "WebFetch 적대적 검증 완료" 주장 vs PSI-Bench arXiv 2604.25840, PsychEval 2601.01802, TherapyGym 2603.18008 등 미래시점 ID 세션 내 web 검증 불가. 핵심 수치(d=0.72, ICC 0.853, 77.2%)가 이 논문에 의존하는데 ICC 0.853은 출처가 "D3-4 인용"으로만 적혀 추적 불가. +- **수정방향:** 신청서·공문 제출 전 모든 arXiv ID 실재 확인, preprint vs peer-reviewed 구분, 인용 수치 원문 캡처 증빙. 미확인은 "preprint, 미검증" 라벨. ICC 0.853을 1차 논문으로 명기. + +### F-25. [데이터·윤리] Presidio 발화단위 마스킹이 사례단위 준식별자 조합을 못 가림 — 미성년 자살사례 재식별 위험 +- **카테고리:** 데이터·윤리 +- **병합 출처:** data M5 +- **근거:** §3.3 Presidio 마스킹은 직접식별자만. taxonomy CCD "고2 남학생+자퇴+등교거부+자살사고+형비교+이모양육+맞벌이" 조합 = 준식별자 묶음으로 소집단 재식별 가능. PIPC 가명처리의 재식별 위험평가 부재. +- **수정방향:** 사례 메타 재식별 위험평가(motivated intruder test), 학년/나이 범주화, 호소문제 일반화. 학습원천은 원사례 아닌 합성변형(F-05 연동). + +### F-26. [기술결함] 교수 검수 weighted-κ→골든셋 승격 루프가 20명 단회기에서 통계 성립 불가 (순환의존) +- **카테고리:** 기술결함 / 데이터·윤리 +- **병합 출처:** integration F6 +- **근거:** §2.3/§3.2 "κ≥0.6/ICC≥0.75 게이트 통과분만 골든셋", 재귀학습이 이 골든셋을 코퍼스로 씀(순환). 50~수백 발화로 안정적 κ/ICC 산출 어렵고, 0615 89라벨을 페르소나+평가 정답 이중사용(§8 액션4) = train/test 누수. +- **수정방향:** 재귀학습을 Phase 3 산출물 아닌 "파이프라인 구축+소량 1차 산출"로 톤다운. 0615를 페르소나 시드와 평가 정답으로 분리. 골든셋 목표 표본 명시, 미달 시 "인프라만 검증, 효과는 후속" 명문화. 1라운드는 교수 단독 라벨로 시드. + +### F-27. [오케스트레이션·구현능력/기술결함] 20주에 음성+3역할+페르소나3종+RAG+재귀학습+안전분류기가 Phase 2(7주)에 동시 압축 — 자기 부록 일정과 충돌 +- **카테고리:** 오케스트레이션·구현능력결함 / 기술결함 +- **병합 출처:** integration F7 +- **근거:** Phase 2(9~15주, 7주)에 트랙 A(음성)·B(3역할+OAuth BFF)·C(페르소나+분류기)·D(재귀+IAA) 병렬. 그러나 음성설계서 §8은 음성만 20주 전체(M4 통합 13~17주), 3AI §7 프로토타입 4~6주. **음성 단독 로드맵 20주를 7주 한 트랙에 욱여넣음**. "기존 풀스택팀(JS 추정)"이 FastAPI/asyncio/Presidio/pgvector/Deepgram WS/AudioWorklet을 7주 동시 습득 가정. +- **수정방향:** Phase 2 분할 — 2a(3역할+OAuth+페르소나, 9~13주), 2b(음성 캐스케이드 1페르소나, 13~17주). 재귀학습 IAA·자살분류기 정밀화는 Phase 3 이연. 음성 M-단계와 Phase 주차를 **단일 간트로 통합**(`feedback_proper_backlog_planning`). + +### F-28. [원래요구위반/오케스트레이션] 한신대 SSO 미확인 블로커가 로드맵 게이트에 미반영 +- **카테고리:** 오케스트레이션·구현능력결함 +- **병합 출처:** security M2 +- **근거:** auth설계서 §2.2/R11 한신대 SSO 프로토콜(OIDC vs SAML)·클레임 미확인. §8 액션#1은 공문이나 Phase 2 "BFF OAuth 2.1"을 태연히 배치 → 납품 직전 SAML 어댑터 미예산 작업 폭발 위험. (사용자 OAuth 2.1+BFF+PKCE 설계 자체는 견고 — 요구2 충족.) +- **수정방향:** 공문 질의를 Phase 0 게이트 격상(IRB 면제로 SSO가 최장 리드타임 블로커). Google OIDC 단독 진행하되 Authlib provider 뒤 SAML SP 어댑터 스캐폴드 선반영. "OAuth" 용어가 (사용자 로그인)과 (엔진 Max OAuth) 혼용되니 분리 명시. + +### F-29. [기술결함/원래요구위반] 폴백 시 LLM 토큰 비용 추산 통째 누락 — "claude -p라 0원"이 폴백에서 붕괴 +- **카테고리:** 기술결함 +- **병합 출처:** security M3 +- **근거:** 음성 §7.1 "로컬 claude -p라 한계비용 0, 폴백 시 별도"로 폴백 비용 증발. R9 한국어 토큰 35% 팽창. Opus 4.8 입력$5/출력$25 per 1M, deep-loop이 전체 회기 채점 → 폴백 시 LLM 비용이 STT/TTS 압도 가능. 회기당 비용 한 번도 추산 안 됨. +- **수정방향:** 폴백 회기당 LLM 비용 상한 실측(Anthropic count_tokens, tiktoken 금지). 프롬프트 캐싱 필수화(L0~L2 정적 → cache_control, Opus 4.8 최소 프리픽스 4096토큰 검증, 읽기 0.1x로 ~90% 절감). deep-loop는 폴백 시 batch API(50% 할인). cost_usd turn 적재 + 폴백 비율 모니터. + +### F-30. [기술결함] visible_to[] / RBAC×cohort / RLS 강제 메커니즘이 3개 설계서에서 명명·모델 불일치 +- **카테고리:** 기술결함 +- **병합 출처:** integration F9 +- **근거:** §3.2 `visible_to[]`(AI 정보비대칭) vs auth-rbac RBAC role+cohort(인간 권한)가 같은 "누가 무엇을 보나"를 두 독립 메커니즘으로 구현, 교차점 미정의. auth-rbac은 RLS를 "2차 강화"로, 마스터플랜 §0은 "RLS 이중강제 기본"으로 — **또 모순**. §3.2 스키마에 cohort·account_linking 없음. +- **원래요구위반:** 요구8(3페이지 권한격리) 강제 메커니즘 비일관. +- **수정방향:** visible_to[](AI)와 RBAC×cohort(인간)를 다른 레이어로 명시 분리, 교차점(인간이 turn 읽을 때 두 게이트 AND)을 단일 enforcement 미들웨어로 통일. RLS 기본/2차를 하나로 확정. Phase 0 스키마에 cohort/account_linking/audit_log 포함. + +### F-31. [데이터·윤리] RAG 4종 용도가 단일 pgvector 1024d로 뭉뚱그려짐 — BGE-M3 멀티벡터/sparse/맥락 컬럼 부재 +- **카테고리:** 기술결함 +- **병합 출처:** integration F12 +- **근거:** §3.6은 RAG 3종 "서로 다른 RAG" + BGE-M3 하이브리드(dense+sparse+ColBERT) 명시하나, §3.2는 `rag_chunks(embedding vector(1024))` 단일 dense 컬럼. sparse·ColBERT·Contextual 맥락문장·리랭커 입력 담을 곳 없음. 3AI §4.3은 3인덱스+graphify MCP로 또 다름. +- **수정방향:** `rag_chunks`에 `index_kind`·`sparse_vec`·`context_prefix`·`colbert_vecs` 추가. 하이브리드 검색 SQL 계약+리랭커 경계 §3.6 명시. graphify-lambda vs pgvector 택일(둘 다면 동기화 계약). + +--- + +## 🟡 MINOR 등급 + +### F-32. [데이터·윤리] KPI 임계치 문서 간 불일치 (κ/ICC ≥0.80 vs ≥0.6/0.75) +- **출처:** data m1. 설계서 §1.2 "≥0.80(39편 메타리뷰)" vs §3.3/§7 "현실치 ≥0.6/0.75". **수정:** 0.6/0.75로 통일 또는 "외부벤치 목표 0.80 vs 파일럿 현실 0.6" 명시 구분. + +### F-33. [데이터·윤리] "환각률 ≤5%(목표 ≤1%)" 측정 정의 부재 +- **출처:** data m2. 판정 주체가 LLM-judge면 F-06(judge 타당도 미검증)과 순환. **수정:** 환각 판정 인간 검수 고정 또는 judge 환각판정 정확도 별도 보고. + +### F-34. [데이터·윤리] CCD core_belief 3유형 중 unlovable 누락 + P1에 2개 동시 부여 +- **출처:** data m3. Patient-Ψ 원설계는 단일 우세 신념. **수정:** 우세 1개+보조 구조화, 3유형 커버리지 페르소나 백로그 반영. + +### F-35. [데이터·윤리/원래요구위반] "재귀적 학습" 메커니즘 미정의 — 축적 ≠ 학습 +- **출처:** data m4. JSONL export까지만, 그 데이터로 무엇을 fine-tune/few-shot 업데이트하는지 루프 닫힘점 없음. **원래요구위반:** 요구5 "재귀적으로 AI가 학습"의 구현체 부재. **수정:** "골든셋 누적→few-shot 풀 자동갱신→평가 프롬프트 업데이트" 명시 루프로 정의(fine-tuning 여부 1차 범위 결정). + +### F-36. [기술결함] 프롬프트 캐싱 "59~70% 절감"이 Opus 4.8 최소 프리픽스 4096토큰 미달 시 silent miss +- **출처:** security m1. **수정:** `usage.cache_read_input_tokens`로 히트 검증, 페르소나+RAG 프리픽스가 4096토큰 넘는지 확인, silent invalidator(타임스탬프) 추적. 측정 후 절감률 보고(`feedback_measure_before_report`). + +### F-37. [기술결함/데이터·윤리] secrets git-ignore만으로 미성년 데이터 키관리 불충분 — 가명↔원본 매핑 격리 미명세 +- **출처:** security m2. **수정:** 키는 secrets manager/OS 키체인, 가명 매핑 테이블은 암호화 DB+2인 승인 명세화. 운영 계정류는 정해진 위치만(`feedback_obsidian_secrets_folder`), 새 폴더 임의 생성 금지. + +### F-38. [기술결함] 위기분류 "수련생 실제위기 vs 페르소나 연기" 판정 계약 미정의 (음성 화자라벨 경계케이스) +- **출처:** integration F11. 화자=counselor의 모든 위기발화를 실제위기로 에스컬레이션하면 false-positive로 시뮬레이션 중단 남발. **수정:** 입력계약 `{speaker, stage, text_masked, is_roleplay_context}→{risk_level, escalate}`, "본인+1인칭 현재시제+시뮬레이션 메타밖" 결정론 룰 우선. Phase 2 DoD에 false-positive 케이스 포함. + +### F-39. [기술결함] `--bare` 재현성 vs 모드A 정액 트레이드오프가 평가루프 신뢰도에 미치는 영향 미정량 +- **출처:** engine F-8. 평가루프(③deep)에 `--bare` 필수라면서 정액 모드(비-bare)는 글로벌 CLAUDE.md/hooks 로드로 재현성 위협. **수정:** ③deep만 API키+`--bare`(또는 Messages API)로 재현성, ②내담자는 정액. 역할별 인증/플래그 분기 명시. + +### F-40. [기술결함/원래요구위반] 단일호스트 모놀리식 vs 요구12 "스케일업" 정의 갭 +- **출처:** security m3. K8s 금지는 20주에 합리적. **수정:** 1차 현 설계 유지, "스케일업=클라우드 VM 이전+compose replicas"로 윤찬과 정의 합의. K8s는 2차 백로그. + +--- + +## 부록. 자기반증으로 폐기한 공격 (투명성) + +각 렌즈가 약하다고 판단해 버린 결함 — Timescale 미도입(PG16으로 충분), pgvector RAG(벡터DB 미지정), 모노레포 과설계(요구10 대비 정당), DSM 진단명 비노출(학술적으로 옳음), 0-5 vs CTRS 0-6(분리운영 방어됨), React 19 미성숙(2026 안정화), Presidio 한국어 NER(M5로 통합), s2s 탈락이 데이터 렌즈상 요구위반(transcript 보존이 재귀학습엔 정당 — 단 *결정권 월권*은 F-08로 살림), BGE-M3 rag-memory 충돌(역할분리 명시), Docker 6컨테이너 과다(20명 규모 합리적). + +--- + +## 종합 판정 + +| 카테고리 | 결함 수 | 핵심 ID | +|---|---|---| +| **원래요구위반** | 10 | F-01·02·03·08·09·10·11·12·28·35·40 | +| **기술결함** | 14 | F-07·13·14·15·19·20·21·29·30·31·36·38·39·40 | +| **오케스트레이션·구현능력** | 5 | F-11·16·17·18·27·28 | +| **데이터·윤리** | 12 | F-03·04·05·06·20·22·23·24·25·26·31·32~35·37 | + +**BLOCKER 7건:** F-01(엔진 바꿔치기) · F-02(음성 격하) · F-03(마스킹 우회) · F-04(n=1 골든셋) · F-05(미성년 재귀학습 동의) · F-06(타당도 미검증 평가) · F-07(음성 WSS 토폴로지 누락) + +**최우선 시정 3건:** +1. **F-01 — 엔진 기본값을 claude -p로 복원.** 윤찬 요구의 심장. 비용/동시성/rate limit 변명 셋 다 약함(정액 한계비용 0, 프로세스 풀+`--input-format stream-json` 상주로 동시성 해결, 20명 규모 근거 빈약). 마스터플랜과 하위 설계서 2종의 엔진 결정을 **하나로 통일**. +2. **F-04+F-05+F-06 결합 — 시스템 전체가 동의 미확보 미성년 자살사례 1건 위에 서 있고, 타당도 미검증 평가 AI가 수련생을 먼저 가르친 뒤 사후에 신뢰도를 잰다.** "골든셋·빡세게·적대적 검증 완료"가 빈약한 데이터 토대와 순환 검증을 학술 외관으로 덮음. +3. **F-11 — IRB 본문 전수 정리.** 헤더만 고치고 본문(Phase 게이트·액션·체크리스트) IRB 의존성 방치 = 부분 수정 누락. + +**메타 결론:** 4개 하위 설계서가 서로의 결론을 부정하는 상태(엔진·음성전송·스키마·권한모델 모두 모순)라 "단일 기준 문서(SoT)"라는 전제 자체가 아직 안 지켜졌다. `feedback_proper_backlog_planning` 원칙상 통합 SoT 재정렬이 선행돼야 한다. diff --git a/infra/README.md b/infra/README.md new file mode 100644 index 0000000..afc777b --- /dev/null +++ b/infra/README.md @@ -0,0 +1,3 @@ +# infra + +Docker Compose: web · api · postgres(pgvector) · voice-gateway. chanpaca.net 외부노출.