음성 재생과 운영 배포 정리
|
|
@ -14,11 +14,20 @@
|
|||
|
||||
- [x] **공용 셸 사이드바 구분선 전체 높이** — (2026-06-26 처리) `.vg-nav border-right`(sticky·뷰포트 높이)를 제거하고, `.vg-shell__body` 컨테이너 배경 하어라인(`linear-gradient` 1px×100%, `--nav-cur`로 그리드·구분선 폭 동기화)으로 본문 그리드 전체 높이를 따라가는 세로 구분선을 그린다. 1024 축소폭은 `--nav-cur: var(--nav-w-collapsed)`로, 720 이하(상단 가로바 전환)는 `background-image:none`으로 정합. `components/shell/shell.css`. **검증: `npm run typecheck` PASS + `vite build` PASS.** 시각 회귀 게이트(gate 7/7 + 포커스 E2E + session-layout 8/8)는 web+api+DB 스택(Docker 미기동·secrets 부재)으로 이 환경 미실행 — 스택 가용 시 1회 시각 확인 권장.
|
||||
- [x] **세션 종료 UX와 dark theme SSOT** — (2026-06-27 처리) 오발동과 모바일 조작 불안을 줄이기 위해 세션 하단의 드래그형 `SlideToEnd`를 명시 확인 다이얼로그로 교체했다. Topbar/Settings의 `vignette.theme` 읽기·저장·`data-theme` 적용은 `apps/web/src/lib/theme.ts` 단일 경로로 통합했고, 저장값이 없으면 dark 기본값을 부팅 전에 적용한다. API 기본 preference `system`은 Settings에서 light로 오해하지 않고 현재 초기 테마를 따른다. 회기 리뷰·설정 포함 주요 페이지의 흰 섹션 잔재도 dark 작업면으로 맞췄다. **검증: `npm run typecheck`, `npm run build`, `npx playwright test e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts e2e/avatar-expression.spec.ts e2e/session-review.spec.ts e2e/settings.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run --workers=1` 36 passed. 추가 시각 검사: 7개 주요 화면 x 3뷰포트 캡처 21장, horizontal overflow 0, 큰 흰 CSS 배경면 0.**
|
||||
- [x] **P1 세션 아바타 컨셉 보드 임시 정렬** — (2026-06-27 처리) 긴 머리 PSB 기본값이 현재 제품 컨셉과 어긋나 active session 첫인상이 흔들렸다. 정식 Image #2 재생성 전까지 `docs/design-concepts/generated/09-avatar-pattern-elements.png`에서 크롭한 래스터 `apps/web/public/avatar/p1-concept/neutral.png`를 P1 기본값으로 연결하고, 데스크톱 스테이지를 3행 구조로 바꿔 현재 상태 배지가 아바타와 겹치지 않게 했다. 이 자산은 누끼가 아니라 크롭이며 Python 분리 처리를 쓰지 않았다. **검증: P1 active session PC/태블릿/모바일 캡처, `rasterArtSet="p1-concept"`, 상태 배지/아바타 겹침 없음, overflow 0.**
|
||||
- [x] **P1 세션 아바타 PSD v2 정렬** — (2026-06-27 처리) 컨셉 보드 크롭 `p1-concept`를 기본값에서 내리고, 사용자 제공 `라투디 여캐_ver2.psd`에서 추출한 `seoyeon-live2d-psd-v2`를 P1 기본값으로 연결했다. `sad` 표정은 PSD의 울상 눈썹, 우는 입, 눈물 파츠를 별도로 합성하고, 데스크톱 스테이지는 3행 구조를 유지해 현재 상태 배지가 아바타와 겹치지 않게 했다. **검증: PSD 파츠 추출 53개, `rasterArtSet="seoyeon-live2d-psd-v2"`, sad 프리뷰에서 눈물/우는 입/울상 눈썹 확인.**
|
||||
- [x] **SEO/GEO 기본 신호 + 회기 리뷰 URL 공유 카드** — (2026-06-27 처리) `index.html`에 canonical/description/Open Graph/Twitter Card/JSON-LD를 추가하고, `robots.txt`/`sitemap.xml`/`llms.txt`를 게시했다. 종료된 학습자 회기는 `POST /sessions/{id}/share`가 공개 토큰을 생성하고 `GET /share/session/{token}`이 서버 HTML로 요약·썸네일·JSON-LD를 내려 URL unfurl을 지원한다. `app.session_share_link`에는 토큰 해시와 sanitized preview payload만 저장하며 원문 축어록·학습자 식별자는 포함하지 않는다. 공유 페이지는 교수자 전달용이라 `noindex`를 유지한다. **검증: `pytest app/test_session_share.py app/test_session_turn_persistence.py -q` 21 passed, `python -m pytest app/ -q` 145 passed, `python -m pytest engine_gateway/ -q` 9 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `PLAYWRIGHT_PORT=5174 npm run e2e` 113 passed.** 배포 후 실제 카카오톡/Slack/메일 unfurl 1회 확인 권장.
|
||||
- [x] **학습자·교수자·운영·페르소나 스튜디오 레이아웃 정렬** — (2026-06-28 처리) 작업형 화면에만 `AppShell wide` 폭 정책을 적용하고, 학습자 홈 회기 목록은 큐형 행으로 정리했다. 교수 콘솔은 검토 대기·위기 알림·페르소나 검수·페르소나 저작실 순서가 DOM과 화면 모두 일치하며, 운영 콘솔 사용자/티켓은 조밀한 운영 행으로 낮췄다. 페르소나 스튜디오는 SSOT 첨부/RAG 생성/항목 편집/검증 큐를 한 작업면에서 유지한다. 리포트와 대표 캡처: `docs/ops/layout-research-2026-06-28/dashboard-layout-alignment-report.md`, `docs/ops/layout-research-2026-06-28/final-gate-*.png`. **검증: `npm run typecheck`, `npm run build`, `npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1` 7 passed, `npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1` 8 passed, `npx playwright test e2e/learner.spec.ts e2e/teacher.spec.ts e2e/admin.spec.ts --project=chromium-desktop --workers=1` 17 passed.**
|
||||
- [x] **관리자 권한 위임 1차** — (2026-06-28 처리) 역할(role)과 관리자 페이지 진입권(`app_user.admin_access`)을 분리했다. `AUTH_SUPER_ADMIN_EMAILS` 기본값은 `yunchan@twentyoz.kr`, `hoonjungkoo@hs.ac.kr`이며 슈퍼 관리자만 `/admin/users`에서 관리자 권한을 부여·회수한다. 구성 슈퍼 관리자는 학습자·교수자·관리자 라우트 전환이 가능하고, 일반 학생/교수 계정도 `admin_access=true`면 우측 상단 관리자 진입이 보인다. **검증: `python -m pytest app/test_auth_providers.py app/test_admin_ops.py -q` 32 passed, `npm run check:api-types`, `npm run typecheck`.**
|
||||
- [x] **회기 아카이브 저장/복원 API** — (2026-06-28 처리) `/learn/history`의 `보관됨`을 실제 학습자별 보기 상태로 연결했다. 종료 회기는 `POST /sessions/{id}/archive`/`restore`로 보관·복원하고, `app.session_archive_state`는 삭제가 아니라 `archived_at`/`updated_at`만 저장한다. `LearnerSessionSummary.archived`, `SessionArchiveResponse`, `LearnerDashboardOverview.archived_sessions`를 OpenAPI에 고정했고, 보관된 회기는 리뷰 대기 행동 큐에서 빠진다. 회기·턴·리뷰·공유 링크·연구/감사 증거는 삭제하지 않는다. **검증: `python -B -m pytest -p no:cacheprovider app/ -q` 168 passed, `python -B -m pytest -p no:cacheprovider engine_gateway/ -q` 9 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npx playwright test e2e/learner.spec.ts --project=chromium-desktop --workers=1` 6 passed.**
|
||||
- [x] **OpenAI TTS voice preset DB map 연결** — (2026-06-28 처리) `app.persona_voice_map`의 OpenAI row를 `/voice/ws` TTS voice 선택에 연결했다. 명시 query preset은 DB map보다 우선하고, 세션 바인딩은 `app.sessions.persona_id/persona_version`으로 voice map을 찾으며, dev persona 생성은 catalog persona id/version map을 사용한다. seed materializer는 기본 OpenAI voice map을 `ON CONFLICT DO NOTHING`으로 생성하고, dev 런타임 스키마 보강은 기존 DB의 `persona_voice_map` 누락도 복구한다. OpenAI가 아닌 provider row는 기존 persona-code fallback으로 안전하게 흡수한다. **검증: `pytest app/test_runtime_policy.py app/test_persona_review.py app/test_voice_service.py app/test_voice_ws.py -q` 75 passed, `pytest app/ -q` 175 passed, `pytest engine_gateway/ -q` 9 passed.**
|
||||
- [ ] **빈상태 컬럼 높이 여백** — session-review 1280 masonry 우측 컬럼, session-prestart 우측 '진행 초점' 컬럼, learner-home 1440 하단 등 데이터 0건 빈상태에서만 두드러지는 컬럼 높이 불균형/여백. 실데이터면 채워진다. **(미착수, 의도적)** 0건 전용 시각 균형이라 러닝 앱에서의 시각 반복 없이 블라인드 수정 시 악화 위험 → 스택 가용 시 시각 확인하며 처리. 빈상태 전용 균형 레이아웃 채택 자체가 별도 폴리시 결정.
|
||||
- [x] **운영 티켓·헬스 샘플 이력 저장 모델 1차** — `/admin` 운영 홈과 `/admin/tickets`가 `app.admin_health_event`·`app.support_ticket` 실제 DB 모델에 연결됐다. `/users/support-tickets`는 인증 사용자의 문제·불만·장애 접수를 저장하고, `/admin/tickets`는 미해결 큐·우선순위·상태 변경을 처리한다. `/admin/uptime`은 상시 SLA가 아니라 관리자 콘솔이 관측한 헬스 샘플 정상률/중단 이력을 반환한다. 가짜 티켓 수·가짜 다운타임 숫자는 계속 금지. **검증: `python -m pytest app/test_admin_ops.py -q` 3 passed, `npm run generate:api-types`, `npm run typecheck`.**
|
||||
- [ ] **운영 티켓·가용성 고도화 정책** — 1차 DB 큐는 연결됐지만 보존 기간, 담당 그룹 자동 배정, 우선순위 escalation, 사용자별 티켓 조회 UI, 상시 synthetic monitor/cron 샘플러, 헬스 이벤트 retention·rollup은 아직 운영 정책 결정과 후속 구현이 필요하다.
|
||||
- [x] **운영 티켓 필터·카테고리 큐·수동 처리 감사 1차** — (2026-06-28 처리) `/admin/tickets`가 status/category/priority/assigned_group/source_path/stale/search 필터를 서버 query로 처리하고, 운영 콘솔은 검색·상태·카테고리·우선순위·담당 그룹·정체 필터와 카테고리 quick queue를 제공한다. 티켓 접수와 관리자 수동 상태/우선순위/담당그룹/해결메모 변경은 `audit.audit_log`에 metadata-only(`support_ticket_create`, `support_ticket_update`)로 남기며, 제목/본문 전문은 감사 로그에 복제하지 않는다. **검증: `pytest app/test_admin_ops.py app/test_runtime_policy.py -q` 28 passed, `npm run generate:api-types`, `npm run check:api-types`, `npm run typecheck`.**
|
||||
- [ ] **운영 티켓 자동 분류·처리 후속** — 중복 티켓 저장 모델(`fingerprint`, parent/child, merge/unmerge), Claude Recipe headless 자동 수정 후보, 관리자 승인 후 이슈 등록·PR/작업 스레드 생성, 처리 결과 audit trail 확장은 아직 설계/승인 필요. 자동 수정은 운영자 승인 전까지 실행하지 않는다.
|
||||
- [x] **learner-home 로딩 스켈레톤 밀도(390)** — (2026-06-26 처리) '연습 대상' 로딩 스켈레톤을 빈 div 단일 셰이머에서 실제 카드 구조(마크 박스 + 이름/메타/요약 2줄 플레이스홀더)를 모사하도록 밀도 보강. `LearnerHome.tsx` (`.lh-skel__box`/`.lh-skel__line*`), reduced-motion 분기 동반 갱신. **검증: `npm run typecheck` PASS + `vite build` PASS.**
|
||||
|
||||
검증 기준: 변경 후 `npm run typecheck`, `e2e/layout-visual-gate.spec.ts`(7/7), 레이아웃 포커스 E2E(54), `e2e/session-layout.spec.ts`(8/8) 무회귀. 이번 dark theme/컨셉 정렬 라운드는 로컬 web+api+DB 스택 가용 상태에서 typecheck/build, Playwright 묶음 36 passed, 21장 캡처의 overflow 0/큰 흰 배경면 0까지 확인했다.
|
||||
검증 기준: 변경 후 `npm run typecheck`, `e2e/layout-visual-gate.spec.ts`(7/7), 레이아웃 포커스 E2E(54), `e2e/session-layout.spec.ts`(8/8) 무회귀. 이번 2026-06-28 대시보드 정렬 라운드는 `npm run typecheck`, `npm run build`, layout gate 7/7, session-layout desktop/mobile 8/8, learner+teacher+admin E2E 17/17까지 확인했다.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -26,11 +35,11 @@
|
|||
|
||||
- [ ] **`vnet.18ka.net` 공개 live 전환** — 코드/스크립트는 `vnet.18ka.net`·`api-vnet.18ka.net`을 허용하도록 갱신 완료. Tailnet 주소 `https://alpaca-home.taile93291.ts.net`은 dev-login까지 검증됐고, public OAuth redirect가 로컬/Tailnet 세션으로 붙지 않는 혼선을 막기 위해 Tailnet Google 버튼과 직접 Google 시작 URL은 `local_oauth_unavailable`로 차단한다. 공개 vnet은 현재 (1) `vnet.18ka.net`/`api-vnet.18ka.net` DNS 부재, (2) cloudflared 현재 인증 컨텍스트가 `18ka.net` zone이 아니라 `chanpaca.net` zone에 route dns를 생성, (3) Google Console redirect URI `https://api-vnet.18ka.net/auth/callback` 등록 필요 때문에 live 완료 아님. 현재 prod API 8001 자체는 `db:true`, `engine:true`로 건강하다. 상세: `docs/ops/tailscale-vnet-runtime-2026-06-27.md`.
|
||||
- [ ] **공개 Google OAuth 실제 `/turn` proof** — 로그인 가능한 계정으로 `storageState` 캡처 후 `E2E_PUBLIC_AUTH=1` + `chromium-public-auth` 1회 통과 필요. (소유자 지시로 이번엔 보류.)
|
||||
- [ ] **음성 캐스케이드 live** — 실제 Deepgram STT / OpenAI TTS provider 키, 물리 마이크, 공개 WSS 50분 양방향 실측. provider 키/하드웨어 부재로 미실행(백엔드 계약·UI synthetic 경로는 단위/E2E로 증명됨).
|
||||
- [ ] **음성 캐스케이드 live** — (2026-06-28 갱신) 로컬 API에서 실제 OpenAI STT/TTS 경로는 live WS smoke로 통과했다(P2 세션, MP3 발화 → transcript 52자 → reply 105자 → TTS binary 66 chunks/266,496 bytes). Session UI는 Web Audio unlock 재생으로 브라우저 autoplay 차단을 회피하도록 보강했고 synthetic mic E2E도 통과했다. 남은 범위는 물리 마이크, 공개 WSS, 50분 양방향 장시간 실측이다.
|
||||
- [ ] **claude_cli ↔ Messages API 폴백 동일성** — `ANTHROPIC_API_KEY`가 있어야 Messages API 경로를 돌려 동일성 검증 가능. claude_cli 경로는 게이트웨이 probe로 live 실측 완료.
|
||||
- [ ] **저항엔진 openness 곡선 DB 실증** — fast-loop 평가의 `client_state_read` → `app.turn_client_state` 적재 경로는 추가됨. 다만 dev DB 실데이터/다수 실턴(real LLM) 기반 openness 곡선 증거는 아직 필요. 현재는 `app.test_state_machine_resistance` 단위테스트와 `app.test_evaluation_persistence` 매핑 테스트로 1차 증명.
|
||||
- [x] **저항엔진 openness 곡선 DB 실증** — (2026-06-28 처리) `scripts/smoke-resistance-openness-db.py`로 실제 API 경로(dev-login→onboarding→P1 세션 2개→각 5턴)를 실행하고 DB `app.session_state`/`app.turns`/`app.turn_client_state`를 직접 조회했다. 공감 5턴은 API curve `0.0→0.0→0.095→0.133→0.171`, DB final `stage=탐색`, `effective_openness=0.17100003361701965`; 조언점프 5턴은 API curve 전부 `0.0`, DB final `stage=라포`, `effective_openness=0.0`, `resistance=0.9580000042915344`. 증거: `docs/ops/resistance-openness-db-smoke-2026-06-28.json`, `docs/ops/resistance-openness-db-smoke-2026-06-28.md`. **검증: `python -B -m py_compile scripts/smoke-resistance-openness-db.py`, `pytest app/test_state_machine_resistance.py app/test_evaluation_persistence.py -q` 9 passed, live smoke PASS.**
|
||||
- [ ] **재부팅 후 watchdog smoke** — `watch-public-runtime.ps1` + Scheduled Task가 재부팅 후 엔진/API/터널을 복구하고 public `/turn`이 통과하는지 실측. 재부팅 불가로 미실행(parser/ check-only 경로는 확인됨). 2026-06-27 점검에서 아직 DNS가 없는 `api-vnet.18ka.net`은 기본 watchdog 검사에서 제외했고, 개통 후 `-AdditionalPublicHealthUrls`로 명시 추가하도록 runbook을 보강했다.
|
||||
- [ ] **상주 엔진풀 RSS 실측** — probe로 TTFT/세션재사용/cost는 live 확인. 프로세스 RSS(메모리)는 별도 OS 계측 항목으로 남김.
|
||||
- [x] **상주 엔진풀 RSS 실측** — (2026-06-28 처리) gateway 세션 1개를 열고 `claude -p` 자식 프로세스 RSS를 Windows WorkingSet 기준으로 계측했다. `health.sessions=1` 상태에서 gateway `python.exe` PID 51592는 after-turn RSS 16.2MB, 상주 `claude.exe` PID 51688은 RSS 363.3MB/Private 628.8MB였다. Claude CLI plugin runtime `bun.exe` 자식 2개는 합계 RSS 178.6MB였다. 세션은 측정 직후 `DELETE /session/{id}`로 종료했다. 증거: `docs/ops/engine-rss-smoke-2026-06-28.md`.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -44,12 +53,14 @@
|
|||
- [ ] **재귀학습 fine-tuning 범위** — few-shot 자동갱신만 / fine-tuning 포함. 영향: 동의서·데이터셋 품질·운영 리스크.
|
||||
- [ ] **자유연습 기본값** — 기본 ON(+피드백 권장 배지+교수자 토글) / 교수자 승인 후 ON. 영향: 학습자 자율성·평가 품질·안전 정책.
|
||||
- [ ] **추가 축어록 수급 규모·일정** — 목표 N건/일정 확정. 영향: 평가 타당도·페르소나 다양성·Phase 3 측정력.
|
||||
- [x] **백엔드 언어 방향** — (2026-06-28 결정) Node.js를 장기 교체 대상과 신규 분리 서비스 우선 스택으로 삼되, 현재 FastAPI 전면 재작성은 납품 일정 리스크라 보류한다. 전환은 계약 우선 strangler 방식으로 진행한다. 1차 경계로 `app/contracts/engine_gateway.py`를 추가해 FastAPI client와 현재 Python gateway가 `/v1/generate`, `/v1/stream`, SSE `token/done/error` 계약을 공유한다. 결정 기록: `docs/decisions/backend-node-transition.md`. **검증: `python -B -m py_compile app/contracts/engine_gateway.py app/engine_client.py engine_gateway/gateway.py`, `python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py -q` 11 passed.**
|
||||
|
||||
---
|
||||
|
||||
## B4. 외부 거버넌스 — 한신대/데이터 steward 서면 증거
|
||||
|
||||
- [ ] **한신대 데이터/SSO 거버넌스 게이트** — SSO claim mapping, 추가 축어록 수급, 미성년 원본 활용동의, 개인정보 처리방침. 로컬 문서 골격 `docs/ops/hanshin-data-governance-gate.md` 준비됨, 외부 서면 증거 필요. (소유자 지시로 이번엔 보류.)
|
||||
- [ ] **L1 스택 정합성·저작권 등재 문서 반영** — 백엔드 방향은 Node.js 교체 가능성 우선으로 결정됐지만, 신청서/저작권 등재 문서에는 현재 FastAPI 유지 사유와 계약 우선 전환 계획을 반영해야 한다. 로컬 결정 기록은 `docs/decisions/backend-node-transition.md`; 외부 제출/등재 증거가 필요하다.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -62,14 +73,17 @@
|
|||
## 이번 세션에 닫은 것(참고)
|
||||
|
||||
- 레이아웃 시각 수용: 게이트 7/7 + 적대적 재검수 7/7 accept + 코스메틱 폴리시(공용 셸 1건 제외) — DONE.
|
||||
- live 운영 증거: engine config 운영값, 상주 엔진풀 probe(TTFT/cost/세션재사용), Postgres RLS/audit smoke 5 checks PASS, turn cost telemetry(app.turns 13행) — DONE.
|
||||
- live 운영 증거: engine config 운영값, 상주 엔진풀 probe(TTFT/cost/세션재사용), 상주 엔진풀 RSS 실측, Postgres RLS/audit smoke 5 checks PASS, turn cost telemetry(app.turns 13행) — DONE.
|
||||
- P1 서연 음성 아트 PoC: Higgs v3 무참조 synthetic seed + 5개 정서/속도 변주(mp3/wav) 생성. 실존 reference voice 미사용. `/voice/ws`에는 dev-only sample TTS provider로 연결했고 live text_turn smoke에서 `p1-sample-poc` binary 20 chunks/78,573 bytes 수신 확인. 이 provider는 TTS override만 담당한다(마이크/STT는 OpenAI 키 필요). 프로덕션 탑재는 라이선스/권리 결정 전 금지.
|
||||
- 부수: RLS smoke 스크립트 cleanup RLS 컨텍스트 버그 수정, `vignette_app`(NOBYPASSRLS) dev 역할 프로비저닝.
|
||||
- 평가 SSOP 2차: `TurnRecord.evaluation`을 `app.feedback_scores`/`turn_technique`/`turn_client_state`/`supervisor_comment`/`alternative_utterance`로 정규화 적재·복원하고, submit/stream/voice 저장 경로를 `app.turn_runtime`으로 공통화했다. 원시 평가 테이블은 evaluator/admin RLS로 제한하고 서버가 evaluator 컨텍스트로 적재한다. `app.turns` insert는 `RETURNING id` 확인으로 silent success 제거. **검증: `pytest app/test_evaluation_persistence.py -q` 6 passed, 관련 회귀 45 passed, `pytest app/` 119 passed, `pytest engine_gateway/` 7 passed.**
|
||||
- 계약 SSOT 4차: FastAPI OpenAPI를 `apps/web/src/lib/api.gen.ts`로 생성하는 `npm run generate:api-types`와 stale 체크용 `npm run check:api-types`를 유지한다. `TurnResponse`/`SessionEndResponse`에 이어 `MeResponse`, `AuthConfigResponse`, `ConsentResponse`, `PersonaSummary`, `PersonaReviewSummary`, admin/user/engine DTO, review leaf DTO, `CrisisResourceResponse`, teacher safety/growth leaf DTO, `LearnerSessionsResponse`, `SessionReviewResponse`, worksheet aggregate, `TeacherDashboardResponse`, `TeacherSessionSummary`, `TeacherLearnerGrowth`, `SessionStartResponse`, `SessionDetailResponse`, `SessionDetailTurn`를 생성 타입 alias로 전환했다. 세션 응답 stage는 `StageLabel` enum으로 OpenAPI에 고정했고, generated optional/default 차이는 UI form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. `.github/workflows/api-contract.yml`은 PR/master push에서 `npm run check:api-types` drift를 막는다. **검증: `npm run check:api-types`, `npm run typecheck`, `npm run build`, `python -m pytest app/test_session_turn_persistence.py -q` 16 passed, `learner.spec.ts + session-review.spec.ts + teacher.spec.ts + layout-visual-gate.spec.ts` 32 passed.**
|
||||
- 라이브 코칭 1차 + 이력 UI/RAG sync: `app/services/live_coach.py`, `data/kb/live_coaching_workbook_0615.json`, `data/kb/live_coaching_sources/*.json`, `POST /sessions/{id}/live-coach`, `GET /sessions/{id}/live-coach`, `POST /kb/live-coach/source-packs/sync`, `app.live_coach_events`를 추가해 0615 사례개념화 워크북·DSM·공식 지침 요약 기반 코칭을 구조화했다. 세션 UI는 코칭 모드에서 AI 코치 아바타 말풍선, 근거 모달, 학습자 발화 우측 `C` 마커, 채팅 위 스크롤 이력 오버레이를 제공한다. 저장 payload는 PII 마스킹 excerpt와 코칭 구조화 JSON으로 제한한다. 공식 자료는 허가된 source pack의 version/citation/summary로만 코칭에 넣고 장문 원문·공식 문항 재현은 하지 않는다. 같은 source pack은 관리자 sync로 evaluator 전용 RAG에 증분 색인한다. **검증: `python -m compileall app -q`, `pytest app/test_live_coach_sources.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q` 27 passed, `npm run generate:api-types`, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npm run e2e -- e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts` 15 passed. 로컬 DB smoke: sync 4 sources/15 chunks embedded, 재호출 skipped_unchanged 4, `official_suicide_risk_guidelines` source_id 필터 검색 성공.**
|
||||
- 계약 SSOT 4차: FastAPI OpenAPI를 `apps/web/src/lib/api.gen.ts`로 생성하는 `npm run generate:api-types`와 stale 체크용 `npm run check:api-types`를 유지한다. `TurnResponse`/`SessionEndResponse`에 이어 `MeResponse`, `AuthConfigResponse`, `ConsentResponse`, `PersonaSummary`, `PersonaReviewSummary`, admin/user/engine DTO, review leaf DTO, `CrisisResourceResponse`, teacher safety/growth leaf DTO, `LearnerSessionsResponse`, `LearnerDashboardResponse`, `SessionReviewResponse`, worksheet aggregate, `TeacherDashboardResponse`, `TeacherSessionSummary`, `TeacherLearnerGrowth`, `SessionStartResponse`, `SessionDetailResponse`, `SessionDetailTurn`를 생성 타입 alias로 전환했다. 세션 응답 stage는 `StageLabel` enum으로 OpenAPI에 고정했고, generated optional/default 차이는 UI form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. `.github/workflows/api-contract.yml`은 PR/master push에서 `npm run check:api-types` drift를 막는다. **검증: `npm run check:api-types`, `npm run typecheck`, `npm run build`, `python -m pytest app/test_session_turn_persistence.py -q` 16 passed, `learner.spec.ts + session-review.spec.ts + teacher.spec.ts + layout-visual-gate.spec.ts` 32 passed.**
|
||||
- 위기게이트 109 1차: 실제 자해·자살 신호는 LLM/엔진 호출 전 중단하고 `crisis_resource(109)`와 `conversation_stopped`를 REST/SSE/voice 응답에 싣는다. `app.safety_events` detail 적재와 교수자 대시보드 안전 알림 큐까지 연결했다. 안전 이벤트 DB insert payload도 회귀화했다. 임상 문안, 실시간 push/메일, 지역 자원 확장은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 20 passed, `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed.**
|
||||
- 교수자 성장 추적 1차: `teacher_dashboard`가 정규화된 턴 평가를 기반으로 학습자별 적절성·라포·기술 사용 추이를 집계하고, 교수 콘솔에 최근 회기/항목별 성장 카드와 안전 알림 큐를 함께 표시한다. **검증: `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed, `pytest app/` 119 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`.**
|
||||
- 페르소나 저작 CRUD 2차: teacher/admin 전용 `POST/GET/PUT /personas/drafts` 경로를 추가해 draft 생성·조회·편집·검수요청을 열고, 교수 콘솔에 JSON 초안 작성/불러오기/수정 UI를 연결했다. 승인된 카드만 학습자 catalog에 노출하는 기존 경계는 유지한다. 추가로 `data/personas/P4.json`~`P7.json`을 저장소 관리 `PersonaCard`로 읽어 `materialize_seed_personas()`와 seed fallback catalog에 포함했다. 항목형 저작 UI, 루브릭·이론 콘텐츠 외부화, 임상팀 최종 검수/서면 evidence는 후속. **검증: `python -m pytest app/test_persona_review.py -q` 22 passed, 로컬 dev DB `materialize_seed_personas()` 결과 `approved_codes=P1,P2,P3,P4,P5,P6,P7`, 인증된 로컬 `GET /personas`도 `P1..P7` 반환.**
|
||||
- 교수자 성장 추적/리뷰 상세 진입 1차: `teacher_dashboard`가 정규화된 턴 평가를 기반으로 학습자별 적절성·라포·기술 사용 추이를 집계하고, 교수 콘솔에 최근 회기/항목별 성장 카드와 안전 알림 큐를 함께 표시한다. 종료 회기 리뷰 대기 행과 최근 회기 행은 `/teach/session/:sessionId/review`로 이어지고, 교수자는 회기 리뷰와 사례개념화 워크시트를 읽기 전용으로 검토한다. 학습자 워크시트 저장은 계속 learner 전용이다. **검증: `pytest app/test_teacher_dashboard.py app/test_session_turn_persistence.py app/test_voice_ws.py -q` 21 passed, `pytest app/test_rbac_idor.py app/test_teacher_dashboard.py -q` 9 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1` 4 passed.**
|
||||
- 교수자 검토 상태 1차: `app.session_review_status`에 교수자 회기 검토 상태를 저장하고, teacher dashboard 응답에 `review_status/review_note/reviewed_at`를 포함한다. `PUT /teacher/sessions/{session_id}/review-status`로 교수자 메모 저장과 검토 완료 처리를 수행하며, 완료된 회기는 pending queue에서 제외한다. 교수자/관리자의 `/sessions/{id}/review` 읽기 허용은 유지하고, 학습자 워크시트 저장은 learner 전용으로 계속 제한한다. 교수 콘솔 검토 큐는 숨김 처리하지 않고 상단 triage로 노출하며, 학습자 리뷰 화면에는 교수자 전용 grid 빈칸을 예약하지 않는다. **검증: `pytest app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_learner_dashboard.py -q` 12 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `teacher.spec.ts` 4 passed, `session-review.spec.ts` 2 passed, `layout-visual-gate.spec.ts` 7 passed.**
|
||||
- 학습자 개인화 대시보드 1차: 교수자 성장 집계와 중복되던 점수/라포/기법 계산을 `app.services.session_metrics`로 공용화하고, `GET /sessions/dashboard`가 본인 세션 기반 `overview/growth/persona_progress/achievements/recent_feedback`를 반환한다. 학습자 홈은 누적 회기, 리뷰 대기, 최근 평가, 라포 흐름, 페르소나별 진행, 최근 피드백, 마지막 세션 리캡을 실제 서버 데이터로 표시한다. 성취는 공식 등급/수료가 아니라 실제 연습 milestone만 사용한다. **검증: `pytest app/test_learner_dashboard.py app/test_teacher_dashboard.py app/test_rbac_idor.py -q` 12 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `learner.spec.ts` 6 passed, `session-review.spec.ts` 2 passed, `teacher.spec.ts` 4 passed, `layout-visual-gate.spec.ts` 7 passed, `session-layout.spec.ts` 4 passed.**
|
||||
- 페르소나 스튜디오/RAG 저작 4차: 교수 콘솔의 JSON 패널을 분리해 teacher/admin 전용 `/teach/personas` 3열 스튜디오(좌: 저작 흐름·카탈로그·검수 큐, 중앙: RAG 자료 등록·항목형 편집 탭, 우: 검증·RAG 근거·검수 결정)로 옮겼다. 2026-06-27 UI 정리에서 중복 네이티브 파일 입력을 커스텀 SSOT 첨부 드롭존으로 교체하고, 저작 스테퍼·탭별 작성 가이드·검증 dot 정렬을 보강했다. 레이아웃 근거와 생성 시안은 `docs/ops/layout-research-2026-06-27/persona-dashboard-layout-guideline.md`에 묶었다. `POST /personas/sources`는 첨부/붙여넣기 자료를 PII 마스킹 후 `kb.source/document/chunk`에 evaluator 전용(`visible_to=['evaluator']`, `sensitivity=2`) 근거 문서로 등록하고, `POST /personas/drafts/generate`는 `source_id` 기반 RAG 검색 결과만 생성 프롬프트에 넘긴다. 생성 응답과 draft `source_provenance`에는 source id, doc/content hash, chunk id를 남긴다. P1~P7 시스템 페르소나는 DB 저작 카탈로그의 초기 부트스트랩으로 승격했고, `materialize_seed_personas()`는 누락분만 insert해서 교수 편집본이나 `archived` 보관본을 덮어쓰지 않는다. 교수자는 공개 목록에서 승인본을 다음 버전 draft로 복제해 수정하고, 불필요한 페르소나는 기존 회기 FK 보존을 위해 같은 code family 전체를 `archived`로 보관 처리한다. 원문 격리본(sensitivity=3) 별도 보관, source/version 전용 마이그레이션 runner, prompt-bundle version pinning은 후속. **검증: `python -m pytest app/test_persona_review.py -q` 31 passed, `npm run generate:api-types`, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1` 6 passed, `layout-visual-gate.spec.ts` 8 passed, `session-layout.spec.ts` desktop/mobile 8 passed.**
|
||||
- M1 비언어 이벤트 1차: voice learner turn에 이미 저장하던 `audio_ref`/`silence_ms`/`speech_rate`/`barge_in`을 리뷰 API `nonverbal` 이벤트로 노출하고, 회기 리뷰 축어록에 침묵·발화 속도·끼어듦·음성 입력 칩을 표시했다. 한숨·울음 감지와 live 마이크/STT/TTS 실측은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 17 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `session-review.spec.ts` 1 passed.**
|
||||
- M2 다회기 케이스 아크 1차: DB 세션 생성 시 `(persona_id, learner_id)` 기준 `case_profile`을 upsert하고, `session_no`를 트랜잭션 안에서 원자 증가시키며, `InProcSession.case_id`가 매회 새 `runtime_case_id`가 아니라 안정 `case_id`를 가리키게 수정했다. 시작/submit/stream/voice 경로는 직전 `session_summary` 기반 seed recall을 사용한다. `case_digest`/`pinned_fact` 실적재, episodic embedding writer, trajectory 갱신은 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q` 18 passed, `pytest app/ -q` 119 passed, `npm run check:api-types`.**
|
||||
- C1 사례개념화 워크시트 2차: `SessionReviewResponse.caseWorksheet`와 리뷰 화면 카드가 탐색 11항목·호소 5영역·인지삼제/감정·보호/방해·생물심리사회 목표 초안을 축어록 근거 기반으로 제공한다. 학습자가 편집한 저장본은 `PUT /sessions/{id}/review/worksheet`로 `app.case_worksheet`에 저장되고, 이후 `GET /review`는 `saved_by_learner` 저장본을 자동 초안보다 우선 반환한다. 임상 루브릭, AI 추출/채점, 교수자 검수는 후속. **검증: `pytest app/test_session_turn_persistence.py app/test_rbac_idor.py -q` 25 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, 로컬 API smoke 세션 생성→저장→`GET /review` `saved_by_learner:local smoke saved worksheet`.**
|
||||
|
|
@ -77,7 +91,8 @@
|
|||
- M3 인증 claim 1차: Google/SAML/dev-login이 `AUTH_EMAIL_COHORT_MAP`·`AUTH_DOMAIN_COHORT_MAP` 및 SAML cohort claim을 `cohort_ids`로 전달하고, DB `app_user.external_id`는 provider subject(`google:`/`saml:`/`dev:`) 기반으로 저장한다. 운영 SAML 서명검증, 기관 claim schema/test tenant, deprovisioning audit은 후속. **검증: `pytest app/test_auth_providers.py -q` 22 passed, `pytest app/ -q` 119 passed.**
|
||||
- X2 예산 경고/저비용 평가 라우팅 1차: `ADMIN_USAGE_BUDGET_USD` 설정값을 기준으로 `GET /admin/usage`가 `budget.status=disabled|ok|warn|exceeded`, 사용률, 잔여 예산을 반환하고 관리자 콘솔이 예산 상태 배너를 표시한다. 80% 이상 warn, 100% 이상 exceeded. `EVALUATOR_FAST_MODEL`/`EVALUATOR_DEEP_MODEL` 설정 시 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달하고, 비워두면 기존 gateway default 라우팅을 유지한다. semantic cache·장기 한도 정책은 후속. **검증: `pytest app/test_evaluator_model_routing.py app/test_runtime_policy.py -q` 23 passed, `pytest app/ -q` 122 passed, `npm run check:api-types`, `npm run typecheck`.**
|
||||
- H4 LLM call audit 1차: 상담 생성(generate/stream)·fast-loop 평가·deep-loop 평가의 외부 LLM 호출 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency metadata만 적재한다. prompt/completion 본문은 저장하지 않고 감사 실패는 상담 루프를 막지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log`가 8→11로 3행 증가했다(session `2460d56c-a9cb-4a40-a175-9575d510a5e9`). 공개 Google OAuth 실제 `/turn` proof는 별도 B2 항목에 남긴다. **검증: `pytest app/test_orchestrator_masking.py app/test_evaluation_persistence.py app/test_session_turn_persistence.py -q` 27 passed, `pytest app/ -q` 119 passed.**
|
||||
- H4 동의 게이트 1차: `app_user.consent_at`를 런타임 테이블 readiness와 `SessionUser`/`Principal`에 포함하고, learner 전용 `POST /auth/consent`·`DELETE /auth/consent`를 추가했다. 동의가 없는 learner는 `POST /sessions`와 voice dev persona 시작이 `consent_required`로 막히고, 프론트 세션 시작 전 동의 체크 UI가 먼저 저장하도록 안내한다. E2E dev-login helper는 세션 테스트 전에 동의를 seed한다. 한국어 이름/기관 NER, guardian/legal 서명 동의서 evidence, 공개 Google OAuth 실제 `/turn` proof는 후속. **검증: `pytest app/ -q` 125 passed, `pytest app/test_auth_providers.py app/test_session_turn_persistence.py app/test_persona_review.py app/test_voice_ws.py -q` 64 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, local `/sessions` no-consent 403 + `/auth/consent` 200.**
|
||||
- H4 온보딩·동의 게이트 3차: `app_user`에 이름·소속·학과·학년/직위·전화번호·주소/수령지·닉네임·자기소개·아바타 URL·약관/개인정보 동의 시각·버전 필드를 추가했다. 로그인 직후 `/onboarding` 완료 전에는 전역 `OnboardingGate`가 `/onboarding` 외 모든 앱 URL(`/learn`, `/settings`, `/admin`, `/dev/avatar-preview`, `/login` 포함)을 온보딩으로 이동시키고, 온보딩 중에는 공용 셸 메뉴 없이 가입 정보 입력 폼만 보여준다. learner `POST /sessions`와 voice dev persona 시작은 `onboarding_required`를 먼저 확인하고, 온보딩 저장 시 learner `consent_at`도 함께 세팅한다. 아바타는 `/users/me/avatar`에서 PNG/JPG/WebP MIME·시그니처·3MB 제한을 통과한 파일만 `USER_UPLOAD_DIR/profile-avatars`에 저장하고 DB에는 URL만 보관한다. 약관·개인정보 처리방침은 개인정보 보호법·처리방침 작성지침·안전성 확보조치 기준·약관규제법 취지를 반영한 개발 초안으로 `/users/legal-docs`에서 제공한다. 관리자/교수자 권한은 온보딩 화면에서 신청받지 않고 기존 `AUTH_ADMIN_EMAILS`/`AUTH_TEACHER_EMAILS` allowlist 및 관리자 사용자 관리 경로로만 부여한다. 한국어 이름/기관 NER, guardian/legal 최종 서명 동의서 evidence, 공개 Google OAuth 실제 `/turn` proof는 후속. **검증: `pytest app/ -q` 145 passed, `pytest engine_gateway/ -q` 9 passed, `npm run check:api-types`, `npm run typecheck`, `npm run build`, `npx playwright test e2e/auth.spec.ts --project=chromium-desktop --workers=1` 7 passed, `PLAYWRIGHT_PORT=5174 npm run e2e` 113 passed.**
|
||||
- RAG warm 동시성 안정화: layout/session E2E가 여러 세션을 빠르게 만들 때 BGE-M3 embedder가 동시에 여러 번 지연 로드되며 `tqdm` lock 예외와 API health/dev-login timeout이 반복됐다. `rag.py`에서 embedder load/encode를 process-wide `RLock`으로 직렬화하고, `sessions.py`의 `_warm_rag_caches`를 semaphore 1개로 제한해 warm task가 상담 요청 경로를 막지 않도록 했다. **검증: `pytest app/ -q` 125 passed, `npx playwright test e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts --project=chromium-single-run --project=chromium-desktop --workers=1` 11 passed.**
|
||||
- 공개/로컬/Tailnet 로그인 복구: public API 530 원인은 prod에서 개발 전용 `VIGNETTE_VOICE_POC_SAMPLE_TTS=true`가 fail-close된 것과 DB `app.admin_engine_config` 기본 행 부재였다. 2026-06-27 모바일 502는 cloudflared 로그의 `127.0.0.1:8001` origin refused와 일치했다. `scripts/start-public-runtime.ps1`는 public prod 기동 시 샘플 TTS를 강제로 끄고, 운영 DB에는 `claude_cli`/`127.0.0.1:9099` engine config 행을 복구했다. 추가로 dev/Tailnet에서는 public OAuth callback이 로컬/Tailnet 세션으로 붙지 않으므로 Google 버튼과 직접 시작 URL을 `local_oauth_unavailable`로 차단하고 dev-login만 사용한다. public OAuth state는 HttpOnly 쿠키에 묶인 HMAC 서명 토큰으로 복구해 API 재시작 뒤 콜백이 `invalid_state`로 떨어지지 않는다. callback 실패는 비밀값 없는 reason/status로 남기고, 로그인 화면도 token/state/provider/identity 실패 메시지와 reason code를 분리한다. 2026-06-27 18:57 KST에 Tailnet stale Vite `allowedHosts` 재발 상태를 재확인했고, `alpaca-home.taile93291.ts.net`를 Vite 기본 허용 host에도 포함해 수동 Vite 기동 시 403 재발 가능성을 낮췄다. **검증: `https://vignette.chanpaca.net/login` 200, `https://api-vignette.chanpaca.net/health` prod/db/engine true, public Google auth redirect 302 + HttpOnly state cookie, provider callback error → `/login?oauth=access_denied`, `https://alpaca-home.taile93291.ts.net/login` 200 + `/api/health` dev/db/engine true + `/api/auth/config` 200, env 없는 임시 Vite host-header smoke 200, local/Tailnet Google direct는 `local_oauth_unavailable`, local auth E2E 7 passed, Tailnet auth/dev-login E2E 2 passed.**
|
||||
- Docker dev DB 안전장치: `scripts/dev-up.ps1`가 `vignette-dev-db` healthcheck/pg_isready/role safety를 점검하고, 새 컨테이너 생성 시 `vignette_owner`와 앱 role을 분리해 healthcheck를 붙인다. `infra\.env` compose 필수값 누락도 사전 경고한다. `scripts/dev-down.ps1`은 기본 DB 보존, `-Db` 지정 시 컨테이너 중지로 정리했다. **검증: 현재 Docker DB accepting connections, `vignette_app:false:false`, 기존 컨테이너 healthcheck 없음. 추가 확인 결과 기존 DB는 `POSTGRES_USER=vignette` 기반이고 API startup DDL이 owner 권한을 요구해 런타임 app-role 전환은 마이그레이션 owner/런타임 role 분리 후 진행해야 한다.**
|
||||
- Docker dev DB 안전장치: `scripts/dev-up.ps1`가 `vignette-dev-db` healthcheck/pg_isready/role safety를 점검하고, 새 컨테이너 생성 시 `vignette_owner`와 앱 role을 분리해 healthcheck를 붙인다. `infra\.env` compose 필수값 누락도 사전 경고한다. `scripts/dev-down.ps1`은 기본 DB 보존, `-Db` 지정 시 컨테이너 중지로 정리했다. **검증: 현재 Docker DB accepting connections, `vignette_app:false:false`, 기존 컨테이너 healthcheck 없음. 패키징 smoke에서 fresh compose DB는 owner/app role 분리 상태로 API healthy까지 통과했다. 기존 dev DB는 `POSTGRES_USER=vignette` 기반이라 운영 role 전환은 별도 owner migration 확인 후 진행한다.**
|
||||
- Docker 패키징 정리 1차: API Dockerfile은 저장소 루트 컨텍스트에서 `apps/api`와 `data`를 함께 복사하고, 런타임 코드의 `Path(__file__).parents[...]` 루트 계산은 `app.paths.repo_root()`로 모았다. 웹 Dockerfile은 npm lockfile 기반 `npm ci`/`npm run build`로 고정하고, web/root `.dockerignore`로 host `node_modules`와 런타임 산출물이 이미지에 섞이지 않게 했다. 구현 없는 `rag.server` 사이드카는 compose에서 제거하고, RAG 무거운 의존성은 `INSTALL_RAG=true` build arg로 API 이미지에 opt-in 설치한다. 사용자 업로드는 `apiuploads` 볼륨(`/app/uploads`)으로 분리했다. API 이미지에 로컬 `apps/api/.env`가 복사되어 prod fail-closed 설정을 오염시키던 보안 결함도 `.dockerignore`로 차단했다. API startup은 init-complete DB에서 owner 권한 DDL을 건너뛰고, non-dev에서 schema가 미완성이면 owner-run init/migration과 `scripts/check-deploy-preflight.py`를 요구한다. `admin_engine_config` 행이 없으면 env 기본 엔진 설정으로 기동한다. backend base/RAG requirements는 exact version으로 고정했고, `data/kb` live-coaching source pack은 commit 또는 동일 경로 provision이 필요한 런타임 입력으로 문서화했다. **검증: `docker compose -f infra/docker-compose.yml config --quiet` 더미 필수 env 통과, API/Web image build 통과, API image `.env*` 미포함, prod import smoke 통과, fresh `docker compose up -d --build` smoke 통과(`/api/health` 200, `db:true`, `engine:true`, `api healthy`), `python scripts/check-deploy-preflight.py --skip-db --env-file infra\.env.example --allow-placeholder-secrets` 통과. 실제 이식 전에는 배포지 `infra/.env`에 `APP_DB_PASSWORD`, OAuth client id/secret, `OPENAI_API_KEY`, `SESSION_SECRET` 등 소유자 비밀값을 채워야 한다.**
|
||||
|
|
|
|||
35
docs/ops/engine-rss-smoke-2026-06-28.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Engine RSS Smoke — 2026-06-28
|
||||
|
||||
목적: `engine_gateway`가 만든 상주 `claude -p` 세션 1개의 Windows RSS/Private memory를 실측한다.
|
||||
|
||||
환경:
|
||||
- OS/셸: Windows 11 + Windows PowerShell 5.1
|
||||
- Gateway: `http://127.0.0.1:9099`
|
||||
- Gateway process: `python.exe -m uvicorn engine_gateway.gateway:app --host 127.0.0.1 --port 9099`
|
||||
|
||||
절차:
|
||||
1. `GET /health`로 gateway healthy 및 sessions=0 확인.
|
||||
2. `POST /session`으로 probe 세션 1개 생성.
|
||||
3. gateway PID의 하위 프로세스 트리를 `Get-CimInstance Win32_Process` + `Get-Process`로 계측.
|
||||
4. `POST /session/{session_id}/turn`에 `Reply with exactly OK.` 한 턴 전송.
|
||||
5. 닫기 전 같은 프로세스 트리를 재계측.
|
||||
6. `DELETE /session/{session_id}`로 세션 종료.
|
||||
|
||||
실측 결과:
|
||||
|
||||
| 시점 | PID | 부모 PID | 프로세스 | WorkingSet MB | Private MB | 비고 |
|
||||
|---|---:|---:|---|---:|---:|---|
|
||||
| after_create | 51592 | 31720 | `python.exe` | 16.1 | 46.2 | engine gateway |
|
||||
| after_create | 51688 | 51592 | `claude.exe` | 343.8 | 613.5 | `claude -p --input-format stream-json --output-format stream-json ...` |
|
||||
| after_create | 64468 | 51688 | `bun.exe` | 42.8 | 49.3 | Claude plugin runtime |
|
||||
| after_create | 54580 | 64468 | `bun.exe` | 135.6 | 557.9 | Claude plugin server |
|
||||
| after_turn | 51592 | 31720 | `python.exe` | 16.2 | 46.2 | engine gateway |
|
||||
| after_turn | 51688 | 51592 | `claude.exe` | 363.3 | 628.8 | resident engine session |
|
||||
| after_turn | 64468 | 51688 | `bun.exe` | 42.8 | 49.3 | Claude plugin runtime |
|
||||
| after_turn | 54580 | 64468 | `bun.exe` | 135.8 | 558.5 | Claude plugin server |
|
||||
|
||||
해석:
|
||||
- 상주 엔진 세션 1개의 핵심 `claude.exe` RSS는 한 턴 후 약 363.3MB다.
|
||||
- gateway Python 자체 RSS는 약 16.2MB다.
|
||||
- Claude CLI가 plugin runtime을 함께 띄우는 현재 환경에서는 자식 `bun.exe` 2개가 추가로 약 178.6MB RSS를 사용한다.
|
||||
- 이 값은 Windows 작업 집합 기준이며, 모델/플러그인/Claude CLI 버전과 세션 프롬프트에 따라 변할 수 있다.
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
> 다른 세션에서 이어서 작업하기 위한 인계. imagegen(gpt-image-2)+BiRefNet+파츠 분리 리깅 파이프라인 전체를 여기서 참조.
|
||||
> 상세 원칙은 `CLAUDE.md` §4.
|
||||
|
||||
> 2026-06-27 추가: PSB 레이어 기반 최신본은 `master` 기본 P1 아바타로 연결됐다. 실제 세션과 `/dev/avatar-preview` 기본값은 `rasterArtSet="seoyeon-live2d-psb"`이고, 기존 `seoyeon` 세트는 `/dev/avatar-preview?rig=safe` fallback으로만 확인한다.
|
||||
> 2026-06-27 19:45 추가: 사용자 제공 PSD `라투디 여캐_ver2.psd`를 기준으로 새 파츠 세트 `seoyeon-live2d-psd-v2`를 만들고 P1 실제 세션 기본값으로 연결했다. 이 세트는 컨셉 보드 크롭이 아니라 PSD 레이어 기반이며, `sad` 표정에서 울상 눈썹·우는 입·눈물 파츠를 별도로 합성한다. `p1-concept`는 기본값으로 쓰지 않는다.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -32,12 +32,13 @@
|
|||
- 28표정→클러스터 매핑(`VARIANT_FOR_EXPRESSION`) 내장.
|
||||
- **연결 배선**:
|
||||
- `persona.ts`: `AvatarPersona.rasterArtSet?: string` 추가됨.
|
||||
- `Session.tsx`: `PERSONA_AVATAR_LOOKS.P1 = { rasterArtSet: "seoyeon-live2d-psb", expressionBias: "sad", ... }` + avatar 객체에 `rasterArtSet` 전달.
|
||||
- `Session.tsx`: 현재 실제 세션 기본값은 `PERSONA_AVATAR_LOOKS.P1 = { rasterArtSet: "seoyeon-live2d-psd-v2", expressionBias: "sad", ... }` + avatar 객체에 `rasterArtSet` 전달.
|
||||
- `ClientAvatar.tsx`: `useRaster` 분기(rasterArtSet 있으면 `RasterBust`, 없으면 기존 SVG). `data-render-mode` 속성 추가.
|
||||
- `App.tsx`: `/dev/avatar-preview` 라우트(인증 없음).
|
||||
- **dev 미리보기**: `apps/web/src/pages/AvatarPreview.tsx`(기본 PSB 최신본, `?rig=safe` 기존 safe 세트, `?rig=v3` atlas 세트).
|
||||
- **dev 미리보기**: `apps/web/src/pages/AvatarPreview.tsx` 기본값은 `seoyeon-live2d-psd-v2`. `?rig=psb`, `?rig=v3`, `?rig=safe`로 과거 세트 비교 가능.
|
||||
- **스크린샷 도구**: `apps/web/scripts/avatar-shot.mjs` (`BASE_URL=http://localhost:<port> node ...`).
|
||||
- **현재 에셋(Image #1=긴 머리 버전)**: `apps/web/public/avatar/seoyeon/{neutral,sad,tired,anxious,warm,startled,eyes-closed,speaking}.png` + `parts/`. → Image #2 재생성 시 덮어쓰기.
|
||||
- **현재 실제 세션 에셋**: `apps/web/public/avatar/seoyeon-live2d-psd-v2/parts/*.png`. 소스/재현 스크립트는 `docs/avatar-art/seoyeon/live2d-psd-v2/`.
|
||||
- **보존 에셋(Image #1=긴 머리 버전)**: `apps/web/public/avatar/seoyeon/{neutral,sad,tired,anxious,warm,startled,eyes-closed,speaking}.png` + `parts/`, `seoyeon-live2d-psb`. 비교용으로 남기되 P1 기본값은 새 PSD v2다.
|
||||
|
||||
## 3. 남은 작업 (순서대로)
|
||||
1. Image #2(2.png) 해석 사용자 확인 → ASCII 이름으로 복사: `cp "<2.png 경로>" docs/avatar-art/seoyeon/ref-image2.png`.
|
||||
|
|
@ -51,7 +52,7 @@
|
|||
## 4. 환경 메모
|
||||
- **dev 서버**: 5173 단일 인스턴스로 띄움(백그라운드). 끊기면 `cd apps/web && npm run dev -- --port 5173 --strictPort`. (5173/5174 중복 인스턴스가 "깨진 화면" 원인이었음 — 항상 5173 단일로.)
|
||||
- codex CLI 0.142.2, auth_mode=chatgpt(구독). object-separation venv: `~/.venvs/object-separation` (CPU-only BiRefNet).
|
||||
- 서연=P1은 API 시드(`apps/api/app/services/persona.py`). 웹엔 P1이 `PERSONA_AVATAR_LOOKS`에 있고 rasterArtSet="seoyeon-live2d-psb".
|
||||
- 서연=P1은 API 시드(`apps/api/app/services/persona.py`). 웹엔 P1이 `PERSONA_AVATAR_LOOKS`에 있고 현재 실제 세션 기본값은 rasterArtSet="seoyeon-live2d-psd-v2"다.
|
||||
|
||||
## 5. 미해결/결정 필요
|
||||
- Image #2 캐릭터의 **의상 색/추가 디테일** (비전 도구 불안정 → 사용자 확인 필요).
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 188 KiB |
|
|
@ -0,0 +1,57 @@
|
|||
# 페르소나 저작·대시보드 레이아웃 지침 — 2026-06-27
|
||||
|
||||
## 산출물
|
||||
|
||||
- 기존 화면 캡처: `docs/ops/layout-research-2026-06-27/persona-studio-before-1440x980.png`
|
||||
- 기존 교수 대시보드 캡처: `docs/ops/layout-research-2026-06-27/teacher-dashboard-before-1440x980.png`
|
||||
- 기존 학습자 홈 캡처: `docs/ops/layout-research-2026-06-27/learner-home-before-1440x980.png`
|
||||
- 생성 시안: `docs/ops/layout-research-2026-06-27/persona-studio-generated-layout.png`
|
||||
|
||||
## 판단
|
||||
|
||||
페르소나 저작은 교수 콘솔 안의 보조 카드가 아니라 별도 작업면이어야 한다. 이 기능의 본질은 JSON 입력이 아니라, 실제 기록·교재·가이드 자료를 SSOT로 등록하고, RAG가 추출한 근거를 따라 AI 초안을 만들고, 교수자가 임상·회기·말투·안전·프롬프트 계약을 검수하는 저작 워크플로다.
|
||||
|
||||
현재 캡처에서 문제였던 지점:
|
||||
|
||||
- 파일 선택이 네이티브 input으로 노출되고 중복 렌더링되어 자료 등록 단계가 조잡해 보인다.
|
||||
- 좌측 레일이 목록만 들고 있어 빈 공간이 커지고, 사용자가 현재 저작 단계가 어디인지 알기 어렵다.
|
||||
- 3열 구조 자체는 맞지만 각 열의 역할이 약하다. 좌측은 자료·작업흐름, 중앙은 편집 캔버스, 우측은 근거·검증이어야 한다.
|
||||
- 검증 카드의 상태 dot과 텍스트가 top-align으로 붙어 단문 검증 행에서 불릿이 어긋나 보인다.
|
||||
- 임상·회기 입력 탭에 작성 가이드가 없어 교수자가 어떤 수준의 정보를 넣어야 하는지 UI가 설명하지 못한다.
|
||||
|
||||
## UX 근거
|
||||
|
||||
- 대시보드는 정보를 많이 보여주는 화면이 아니라 사용자가 다음 행동을 빠르게 결정하는 작업면이어야 한다. 참고: [NN/g Dashboard Design](https://www.nngroup.com/articles/dashboards-preattentive/).
|
||||
- 복잡한 교육·운영 앱은 기능 목록보다 핵심 워크플로와 사용자 역할별 작업 큐를 우선해야 한다. 참고: [NN/g Complex Application Design](https://www.nngroup.com/articles/complex-application-design/).
|
||||
- 비교·검토·상태 판단이 필요한 데이터는 카드 묶음보다 표, 큐, 필터, 행 액션이 낫다. 참고: [NN/g Data Tables](https://www.nngroup.com/articles/data-tables/), [Carbon Data Table](https://carbondesignsystem.com/components/data-table/usage/).
|
||||
- 빈 상태는 장식이 아니라 다음 입력을 유도해야 한다. 참고: [NN/g Empty States](https://www.nngroup.com/articles/empty-state-interface-design/), [Carbon Empty States](https://carbondesignsystem.com/patterns/empty-states-pattern/).
|
||||
- 임상 의사결정형 UI는 적절한 정보, 사람, 채널, 시점, 형식을 맞춰야 한다. 참고: [AHRQ CDS Five Rights](https://digital.ahrq.gov/program-overview/research-stories/scaling-and-dissemination-effective-clinical-decision-support).
|
||||
- 클릭 대상은 최소 24px 이상을 지켜야 한다. 참고: [WCAG 2.2 Target Size](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html).
|
||||
|
||||
## Vignette 레이아웃 원칙
|
||||
|
||||
1. 홈/대시보드는 역할별 작업대다. 학습자는 "다음 연습", 교수자는 "검토할 회기와 위험 신호", 관리자는 "운영 이상과 접근 권한"을 첫 화면에서 바로 처리해야 한다.
|
||||
2. 한 화면에는 하나의 1차 CTA만 둔다. 나머지는 행 액션, 세컨더리 버튼, 필터로 낮춘다.
|
||||
3. 카드 남발을 피한다. 반복 비교가 필요한 정보는 큐/테이블로, 개별 상세만 카드로 둔다.
|
||||
4. PersonaStudio는 3열을 유지하되 의미를 고정한다.
|
||||
- 좌측: 자료 큐, 저작 단계, 검수/공개 카탈로그
|
||||
- 중앙: RAG 자료 등록, 탭별 구조화 편집, 저장/검수 요청
|
||||
- 우측: 검증, 생성 근거, 마스킹, 승인/반려
|
||||
5. RAG 첨부 파일은 SSOT다. 원문 파일, 마스킹 결과, source/document/chunk hash, 초안 생성 evidence를 한 작업면에서 추적해야 한다.
|
||||
6. 임상·회기·안전 탭은 빈 입력란만 두지 않는다. 각 탭 상단에 작성 목적, 포함할 정보, 피해야 할 입력을 짧게 제시한다.
|
||||
|
||||
## 컴포넌트 규칙
|
||||
|
||||
- 파일 입력은 네이티브 input을 직접 노출하지 않는다. 숨김 input + 드롭존 + 명시 버튼으로 구성한다.
|
||||
- 스테퍼는 숫자 칩만 나열하지 않는다. 단계명, 목적, 현재/완료 상태가 함께 보여야 한다.
|
||||
- 검증 리스트의 단문 행은 dot과 텍스트를 세로 중앙 정렬한다. 근거 문서처럼 두 줄 이상인 행만 dot을 상단 정렬한다.
|
||||
- 탭은 데이터 섹션 이동이다. 선택 상태가 약하면 사용자는 버튼 묶음으로 오해한다.
|
||||
- 페이지 섹션을 카드 안에 다시 카드로 감싸지 않는다. 반복 아이템, 모달, 프레임이 필요한 도구에만 카드를 쓴다.
|
||||
- 모바일에서는 좌측 레일 → 중앙 편집 → 우측 검증 순서로 내려가며, 파일 업로드와 가이드는 1열로 접는다.
|
||||
|
||||
## 데이터·버전 전략
|
||||
|
||||
- 페르소나에는 `persona_schema_version`, `prompt_bundle_version`, `source_bundle_version`을 분리해 저장해야 한다.
|
||||
- 생성 초안은 어떤 source id, document hash, chunk id, 마스킹 정책으로 생성됐는지 evidence를 남긴다.
|
||||
- 서비스 프롬프트가 바뀌면 기존 페르소나를 즉시 덮어쓰지 않는다. 호환성 검사 → 재생성 후보 표시 → 교수자 승인 후 새 버전 발행 순서로 처리한다.
|
||||
- Docker DB 마이그레이션은 init SQL과 별도로 idempotent migration runner를 둔다. 신규 설치는 init SQL, 기존 배포는 migration runner가 같은 최종 스키마로 수렴해야 한다.
|
||||
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 462 KiB |
|
After Width: | Height: | Size: 384 KiB |
|
After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 882 KiB |
|
|
@ -0,0 +1,38 @@
|
|||
# 대시보드 레이아웃 정렬 리포트
|
||||
|
||||
작성일: 2026-06-28 KST
|
||||
|
||||
## 범위
|
||||
|
||||
이번 라운드는 페르소나 저작 기능을 교수 콘솔의 한 카드로 방치하지 않고, 교육 플랫폼의 실제 작업 흐름 안에 맞추는 데 초점을 둔다. 수정 범위는 학습자 홈/연습/기록, 교수 콘솔, 운영 콘솔/사용자/티켓, 페르소나 스튜디오, 공용 셸 폭 정책이다.
|
||||
|
||||
## 적용 판단
|
||||
|
||||
- 학습자 홈은 "오늘 이어갈 회기"를 첫 결정 영역으로 두고, 하단 회기 목록은 카드 더미가 아니라 상태/대상/시작/작업이 보이는 큐로 낮췄다.
|
||||
- 교수 콘솔은 검토 대기와 위기 알림을 최우선 업무로 올렸다. 페르소나 저작은 별도 스튜디오 진입점으로 남기되, 검수 큐와 연결해 교육 운영 맥락 안에서 보이게 했다.
|
||||
- 페르소나 스튜디오는 3열을 유지한다. 왼쪽은 저작 흐름과 카탈로그, 중앙은 SSOT 첨부/RAG 생성/항목 편집, 오른쪽은 검증/RAG 근거/검수 결정이다. 세 컬럼이 단순 장식이 아니라 역할이 다르기 때문에 현재 구조가 맞다.
|
||||
- 운영 콘솔은 사용자/티켓을 카드형 나열보다 조밀한 운영 행으로 낮췄고, 모바일에서는 내부 스크롤보다 세로 흐름을 우선했다.
|
||||
- 공용 셸에는 `wide` 모드를 추가해 작업형 화면만 넓게 쓰게 했다. 읽기형 화면의 최대 폭 정책은 건드리지 않았다.
|
||||
|
||||
## 증거 캡처
|
||||
|
||||
- `final-gate-learner-home__1440-desktop.png`
|
||||
- `final-gate-learner-home__390-mobile.png`
|
||||
- `final-gate-professor__1440-desktop.png`
|
||||
- `final-gate-professor__390-mobile.png`
|
||||
- `final-gate-admin__1440-desktop.png`
|
||||
- `final-gate-admin__390-mobile.png`
|
||||
- `persona-studio-after1-1440x980.png`
|
||||
- `persona-studio-after1-390x844.png`
|
||||
|
||||
## 검증
|
||||
|
||||
- `npm run typecheck` passed.
|
||||
- `npm run build` passed.
|
||||
- `npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1` 7 passed.
|
||||
- `npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1` 8 passed.
|
||||
- `npx playwright test e2e/learner.spec.ts e2e/teacher.spec.ts e2e/admin.spec.ts --project=chromium-desktop --workers=1` 17 passed.
|
||||
|
||||
## 남겨둔 기준
|
||||
|
||||
페르소나 저작의 핵심 SSOT는 첨부 파일과 RAG source id다. 교수자가 실제 기록, 교재, 가이드를 넘기면 원문을 곧장 프롬프트에 섞는 방식이 아니라, PII 마스킹과 근거 청크 등록 후 생성/검수 기록에 source id, document hash, chunk id를 남기는 현재 구조를 유지해야 한다.
|
||||
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 273 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 263 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 333 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 350 KiB |
|
After Width: | Height: | Size: 188 KiB |
191
docs/ops/resistance-openness-db-smoke-2026-06-28.json
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
{
|
||||
"ok": true,
|
||||
"api_base_url": "http://127.0.0.1:8000",
|
||||
"persona_code": "P1",
|
||||
"empathy": {
|
||||
"label": "empathy",
|
||||
"session_id": "a6c4d731-875e-4023-a369-b07de65f511e",
|
||||
"api_curve": [
|
||||
{
|
||||
"turn_seq": 1,
|
||||
"stage": "라포",
|
||||
"effective_openness": 0.0,
|
||||
"safety_flagged": false
|
||||
},
|
||||
{
|
||||
"turn_seq": 2,
|
||||
"stage": "라포",
|
||||
"effective_openness": 0.0,
|
||||
"safety_flagged": false
|
||||
},
|
||||
{
|
||||
"turn_seq": 3,
|
||||
"stage": "탐색",
|
||||
"effective_openness": 0.095,
|
||||
"safety_flagged": false
|
||||
},
|
||||
{
|
||||
"turn_seq": 4,
|
||||
"stage": "탐색",
|
||||
"effective_openness": 0.133,
|
||||
"safety_flagged": false
|
||||
},
|
||||
{
|
||||
"turn_seq": 5,
|
||||
"stage": "탐색",
|
||||
"effective_openness": 0.171,
|
||||
"safety_flagged": false
|
||||
}
|
||||
],
|
||||
"db_state": {
|
||||
"session_id": "a6c4d731-875e-4023-a369-b07de65f511e",
|
||||
"stage": "탐색",
|
||||
"turn_seq": 5,
|
||||
"effective_openness": 0.17100003361701965,
|
||||
"rapport_credit": 0.8100000023841858,
|
||||
"resistance": 0.5199999213218689
|
||||
},
|
||||
"db_turn_count": 10,
|
||||
"db_client_states": [
|
||||
{
|
||||
"turn_seq": 1,
|
||||
"actor_kind": "human_learner",
|
||||
"client_states": [
|
||||
"defensive",
|
||||
"involuntary",
|
||||
"lack_of_confidence"
|
||||
]
|
||||
},
|
||||
{
|
||||
"turn_seq": 3,
|
||||
"actor_kind": "human_learner",
|
||||
"client_states": [
|
||||
"defensive",
|
||||
"involuntary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"turn_seq": 5,
|
||||
"actor_kind": "human_learner",
|
||||
"client_states": [
|
||||
"affect_contact",
|
||||
"defensive",
|
||||
"responds_to_exploration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"turn_seq": 7,
|
||||
"actor_kind": "human_learner",
|
||||
"client_states": [
|
||||
"involuntary",
|
||||
"lack_of_confidence"
|
||||
]
|
||||
},
|
||||
{
|
||||
"turn_seq": 9,
|
||||
"actor_kind": "human_learner",
|
||||
"client_states": [
|
||||
"defensive",
|
||||
"involuntary",
|
||||
"lack_of_confidence"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"advice_jump": {
|
||||
"label": "advice_jump",
|
||||
"session_id": "5bf60d80-12f5-45ef-99fd-1856d02f6bb4",
|
||||
"api_curve": [
|
||||
{
|
||||
"turn_seq": 1,
|
||||
"stage": "라포",
|
||||
"effective_openness": 0.0,
|
||||
"safety_flagged": false
|
||||
},
|
||||
{
|
||||
"turn_seq": 2,
|
||||
"stage": "라포",
|
||||
"effective_openness": 0.0,
|
||||
"safety_flagged": false
|
||||
},
|
||||
{
|
||||
"turn_seq": 3,
|
||||
"stage": "라포",
|
||||
"effective_openness": 0.0,
|
||||
"safety_flagged": false
|
||||
},
|
||||
{
|
||||
"turn_seq": 4,
|
||||
"stage": "라포",
|
||||
"effective_openness": 0.0,
|
||||
"safety_flagged": false
|
||||
},
|
||||
{
|
||||
"turn_seq": 5,
|
||||
"stage": "라포",
|
||||
"effective_openness": 0.0,
|
||||
"safety_flagged": false
|
||||
}
|
||||
],
|
||||
"db_state": {
|
||||
"session_id": "5bf60d80-12f5-45ef-99fd-1856d02f6bb4",
|
||||
"stage": "라포",
|
||||
"turn_seq": 5,
|
||||
"effective_openness": 0.0,
|
||||
"rapport_credit": 0.0,
|
||||
"resistance": 0.9580000042915344
|
||||
},
|
||||
"db_turn_count": 10,
|
||||
"db_client_states": [
|
||||
{
|
||||
"turn_seq": 1,
|
||||
"actor_kind": "human_learner",
|
||||
"client_states": [
|
||||
"defensive",
|
||||
"involuntary",
|
||||
"negative_self_perception"
|
||||
]
|
||||
},
|
||||
{
|
||||
"turn_seq": 3,
|
||||
"actor_kind": "human_learner",
|
||||
"client_states": [
|
||||
"defensive",
|
||||
"involuntary",
|
||||
"negative_self_perception"
|
||||
]
|
||||
},
|
||||
{
|
||||
"turn_seq": 5,
|
||||
"actor_kind": "human_learner",
|
||||
"client_states": [
|
||||
"defensive",
|
||||
"involuntary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"turn_seq": 7,
|
||||
"actor_kind": "human_learner",
|
||||
"client_states": [
|
||||
"defensive",
|
||||
"negative_self_perception"
|
||||
]
|
||||
},
|
||||
{
|
||||
"turn_seq": 9,
|
||||
"actor_kind": "human_learner",
|
||||
"client_states": [
|
||||
"defensive",
|
||||
"involuntary",
|
||||
"negative_self_perception"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"assertion": {
|
||||
"empathy_final_openness": 0.17100003361701965,
|
||||
"advice_final_openness": 0.0,
|
||||
"empathy_stage": "탐색",
|
||||
"advice_stage": "라포"
|
||||
}
|
||||
}
|
||||
32
docs/ops/resistance-openness-db-smoke-2026-06-28.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Resistance Openness DB Smoke
|
||||
|
||||
Date: 2026-06-28
|
||||
|
||||
## Command
|
||||
|
||||
```powershell
|
||||
C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe scripts/smoke-resistance-openness-db.py --api-base-url http://127.0.0.1:8000 --timeout 240 --out docs/ops/resistance-openness-db-smoke-2026-06-28.json
|
||||
```
|
||||
|
||||
## Result
|
||||
|
||||
The smoke created two fresh P1 learner sessions through the real API path:
|
||||
|
||||
- dev-login
|
||||
- onboarding and consent
|
||||
- `POST /sessions`
|
||||
- five `POST /sessions/{id}/turn` calls
|
||||
- direct DB read from `app.session_state`, `app.turns`, and `app.turn_client_state`
|
||||
|
||||
## Evidence
|
||||
|
||||
| Track | Session | API Curve | DB Final State |
|
||||
|---|---|---|---|
|
||||
| Empathy | `a6c4d731-875e-4023-a369-b07de65f511e` | `0.0 -> 0.0 -> 0.095 -> 0.133 -> 0.171`, stage `탐색` from turn 3 | stage `탐색`, `turn_seq=5`, `effective_openness=0.17100003361701965`, `rapport_credit=0.8100000023841858`, `resistance=0.5199999213218689` |
|
||||
| Advice jump | `5bf60d80-12f5-45ef-99fd-1856d02f6bb4` | `0.0 -> 0.0 -> 0.0 -> 0.0 -> 0.0`, stage `라포` throughout | stage `라포`, `turn_seq=5`, `effective_openness=0.0`, `rapport_credit=0.0`, `resistance=0.9580000042915344` |
|
||||
|
||||
The DB also stored fast-loop `turn_client_state` rows for both sessions. The full JSON output is `docs/ops/resistance-openness-db-smoke-2026-06-28.json`.
|
||||
|
||||
## Verdict
|
||||
|
||||
PASS. The deterministic resistance engine opens the P1 session under empathic responses and keeps the session closed under advice-jump responses. The result is backed by both API turn responses and DB state.
|
||||