기반 정렬 / 운영 안전장치
인프라, 인증 경계, fail-closed, 공개 배포, 회귀 테스트 안정화.
- 상태: 대부분 완료, 공개 `/turn` proof만 남음
- 검증: build + backend 20 + Playwright 72
- 운영: prod API 8001, engine 9099, tunnel 1개
이번 세션은 seed persona 결합 제거, 비개발 fail-closed 설정, compose OAuth 주입, admin 모바일 overflow 수정, persona 인증 경계 보강, 전체 Playwright 재통과, 공개 API prod 전환까지 완료했다. 이후 로컬 5175 런타임 재기동, admin 사용자 관리 E2E 안정화, 학습자 홈 0회 기록 UX, active session 실제 SSE 스트림 전환, 텍스트/음성 실패 턴 미저장, 직접 URL persona 우회 차단, Live2D demo 자산 제거, AI 운영 fail-closed 보강까지 전체 Playwright로 확인했다. 2026-06-26 15:18 KST 기준 공개 API 터널을 복구한 뒤 전체 Playwright 72개가 desktop/mobile/single-run 모두 통과했다. Cloudflare Pages production 배포 `0fe8f3cc`도 완료했고, 커스텀 도메인이 새 asset을 서빙하는 것을 확인했다. 과거 Mao/Haru Live2D demo 자산은 repo와 번들에서 제거했고, `/live2d/*` 운영 경로는 404로 차단했다. 로컬 프로세스는 prod API 8001, engine gateway 9099, cloudflared tunnel 1개만 남기고 dev API와 임시 Vite preview를 내렸다. 다만 이 HTML은 지금까지 Phase 0 상태판처럼 쓰였고, 아래에 Phase 1 이후 전체 계획을 보강했다. 공개 Google OAuth 실제 `/turn` 증명만 storageState 캡처 후 남아 있다.
`docs/MASTERPLAN.md`와 `docs/redteam/MASTERPLAN_REVISIONS.md`를 합친 팀장 판단용 단계표
인프라, 인증 경계, fail-closed, 공개 배포, 회귀 테스트 안정화.
1 페르소나 상담, 실제 턴 저장, 회기말 피드백, 저항 엔진 검증.
학습자, 교수자, 관리자 권한 분리와 페르소나 확장.
STT → 엔진 → TTS 왕복, WSS/터널 안정성, barge-in 로깅.
수련생 파일럿, 사전사후 효과 측정, 재귀학습 데이터셋 산출.
탭별 할 일, 산출물, 검증 기준. 팀장 리뷰는 여기서 다음 작업을 고르면 된다.
지금 단계는 구현을 더 벌리는 단계가 아니라, 운영 증거를 닫고 P1 진입 조건을 고정하는 단계다.
로그인 가능한 계정으로 storageState를 저장한 뒤 public-auth-turn E2E를 실행한다.
`apps/web/node_modules/.tmp/public-auth.json`, public `/turn` 성공 로그
`E2E_PUBLIC_AUTH=1` + `chromium-public-auth` 1회 통과
E2E가 admin engine config를 만질 수 있으므로 배포 전 `mode/url/model` 값을 확인한다.
admin AI 운영 화면 캡처 또는 config API 응답
`claude_cli / http://127.0.0.1:9099 / gateway-default` 의도값 대조
Mao/Haru 샘플, Pixi/Cubism 런타임, 공개 `/live2d/*` 캐시 잔여 접근을 차단했다.
`apps/web/functions/live2d/[[path]].js`, SVG-only avatar
운영 `/live2d/mao/*`, Cubism core `404`
임시 Vite preview와 dev API를 내리고 운영 확인용 프로세스만 유지한다.
9099 engine, 8001 prod API, cloudflared tunnel 1개
`Get-NetTCPConnection`에서 8000/517x/5537 비어 있음
SSO 클레임, 추가 축어록 수급, 미성년 원본 활용동의, 개인정보 처리방침을 P1 진입 전 외부 의존성으로 명확히 둔다.
공문 질의서, 동의 범위 체크리스트, SSO claim 표
IRB 게이트가 아니라 데이터/SSO 게이트로 문서 전수 정합
이전 “완료” 카드 내용을 탭 안에서 항목별로 보존한다.
`session-persistence`, `teacher`, `voice`는 `/api/personas`에서 실제 DB persona를 가져온다.
seed persona 직접 결합 제거, 실제 persona catalog 기반 E2E
persona 관련 E2E가 DB-backed persona로 통과
prod/staging에서 dev login, seed fallback, OAuth 누락, 기본 secret, localhost CORS를 거부한다.
fail-closed runtime config validation
backend runtime policy unittest 통과
`ENVIRONMENT`, Google OAuth vars, `CORS_ORIGINS`, `OPENAI_BASE_URL`을 API 컨테이너에 명시했다.
compose 환경 주입 정리
`docker compose --env-file .env.example config --quiet` 통과
긴 readiness detail을 안정화했고, 사용자 변경은 DB 저장소가 durable할 때만 허용한다.
admin responsive layout, durable store gate, AI health copy
admin manage-users desktop/mobile 2 passed
`db-persistence`는 전역 engine config를 바꾸므로 `@single-run`으로 분리했다.
Playwright single-run tagging
전체 Playwright 72 passed
`docs/HANDOFF.md`, `docs/dev_dashboard.html`, `apps/web/e2e/README.md`를 최신 상태로 맞췄다.
handoff, dashboard, E2E README
최신 커밋 기준 상태/검증/운영 포트 일치
`cloudflared` ingress를 `127.0.0.1:8001` prod API로 돌렸고 public readiness gate가 통과했다.
prod API 8001, tunnel config
`https://api-vignette.chanpaca.net/health` prod/db/engine OK
`scripts/start-public-runtime.ps1`와 `scripts/stop-public-runtime.ps1`로 API 8001과 cloudflared를 재현 가능하게 만들었다.
public runtime start/stop scripts
engine 9099 + API 8001 + cloudflared only
`/personas`는 이제 비로그인 401이다. 로컬과 공개 API 모두 직접 확인했고 E2E 회귀를 추가했다.
authenticated persona catalog boundary
local/public unauth `GET /personas` 401
public 웹에서 `/api/auth/config`가 SPA HTML로 떨어지지 않도록 public API origin fallback을 추가했다.
public API origin fallback
public login screen E2E 1 passed
학습자 홈은 fake 기록 없이 실제 서버 세션 0개를 `기존 회기` 빈 상태와 0 카운트로 표시한다.
empty history UX, zero count, no fake stats
learner screenshots: document overflow 0, empty history visible
`127.0.0.1:5175` web, `8000` API, `9099` engine을 재기동하고 dev-login 서버 세션과 `/learn` redirect를 확인했다.
local dev-login flow proof
desktop/mobile auth flow passed
375px급 active session에서 stage 내부가 1px 넘게 잘리던 문제를 avatar/padding 조정으로 제거했다.
session layout responsive adjustment
session-layout desktop/mobile 8 passed
`POST /sessions/{id}/stream` token 이벤트로만 내담자 자막을 쌓고, HTTP 거절과 SSE error 모두 실패 턴을 transcript와 저장소에 남기지 않는다.
real SSE stream turn path, failed turn discard
session layout/turn E2E passed
음성 회기도 엔진 응답 생성 성공 후에만 learner/client turn을 저장한다. 실패한 transcript는 pending 상태에서 제거한다.
voice failure persistence guard
voice + voice-success + session-layout 15 passed
admin health와 voice degraded reason은 내부 fallback/seed 표현 대신 비영구 런타임 상태를 사용자용 문구로 표시한다.
operator-facing copy cleanup
settings/admin focused E2E 통과
`/learn/session/:code`로 직접 들어와도 DB 원본 persona가 아니면 회기 시작을 막는다.
route guard, degraded/seed fallback regression
unavailable persona code E2E 통과
Mao/Haru 샘플과 Pixi/Cubism 런타임을 공개 배포물에서 제거했다. 세션 화면은 SVG persona avatar만 렌더링한다.
SVG-only avatar, legacy `/live2d/*` 404 function
Mao/Haru/Cubism core routes 404, no Live2D/Pixi chunks
목표는 “P1 내담자와 실제 상담 1회 완주 후 피드백을 받는 공개 시연 가능 상태”다.
claude_cli와 Messages API 폴백이 같은 이벤트 스키마와 cost 규약을 반환하게 한다.
`delta.text`, `result`, `cost` 이벤트 계약과 contract test
동일 입력 → 두 어댑터 정상화 출력 동일성 테스트
`--input-format stream-json` 상주 프로세스를 회기당 1회 기동하는 구조로 검증한다.
TTFT p50/p95, 동시 4회기 RSS/OOM 로그
실측 전 “빠름/검증됨” 표기 금지
claude_cli 경로도 Presidio/가명처리 후에만 stdin으로 들어가게 막는다.
엔진 어댑터 전단 마스킹 미들웨어, 우회 차단 테스트
민감 텍스트 raw 외부 전송 경로 0개
로그인, P1 선택, SSE 상담, 종료, 회기말 리뷰를 한 번에 통과시킨다.
turn DB 적재, session review, cost telemetry
Playwright로 실제 로그인 → 상담 완주 → 피드백 수신
공감 발화와 조언점프 발화 세트를 넣어 openness 곡선을 비교한다.
공감 5턴 vs 조언 5턴 DB 쿼리 결과
공감 시 개방도 상승, 조언점프 시 하락 증거
Phase 1에서 음성을 완전히 뒤로 미루지 않고, 얇은 1턴 왕복 증거를 만든다.
STT transcript, TTS audio, voice turn log
실제 마이크/오디오 1턴 + 실패 시 턴 미저장
음성과 섞지 않고 3역할, 권한격리, 페르소나 확장을 독립 트랙으로 닫는다.
브라우저에 토큰을 저장하지 않는 BFF 세션을 역할 페이지 전체에 통일한다.
Auth Code + PKCE, stateful session, role claim mapping
비로그인 401, 역할 없는 접근 403, 쿠키 보안 속성 확인
RBAC와 cohort 소유권 게이트를 모든 session/review/admin API에 적용한다.
권한 미들웨어, audit log, role route map
타인 `session_id` 직접 입력 → 403 스크린샷
AI 정보비대칭과 사용자 권한을 섞지 않는다. 인간 조회는 RBAC와 cohort AND 게이트다.
RLS 정책, enforcement middleware, 테스트 matrix
AI-only hidden field가 인간 조회 권한을 열지 않음
P1 단일 시드에서 P2/P3로 확장하되 교수 검수 상태와 공개 가능 상태를 분리한다.
persona review workflow, versioning, approval state
미승인 persona는 학습자 시작 불가
1차는 Google OIDC로 가되 한신대 SSO 전환 지점을 Authlib provider로 남긴다.
provider abstraction, SAML placeholder config
Google OIDC 경로 회귀 없이 provider 교체 가능
음성은 “되게 보이는 UI”가 아니라, 전송, STT, EOT, 엔진, TTS, 저장까지 끊기지 않아야 한다.
브라우저 AudioWorklet PCM을 WSS로 보내고, TTS 오디오 청크를 내려받는다.
`voice.chanpaca.net` 또는 단일 도메인 WSS 경로
50분 WSS 양방향 스트림 끊김 실측
interim/final transcript와 상담 튜닝 침묵 임계, semantic EOT를 연결한다.
transcript timestamp, silence_ms, final segment
침묵/말 끊김/짧은 대답 케이스 회귀
persona별 voice_id와 prosody instruction을 저장하고 교체 가능하게 한다.
`voice_persona_map`, provider-agnostic params
P1/P2/P3 voice preset 분리, fallback 정상
중간 끼어들기, 침묵, 말속도 같은 음성 메타를 턴 로그와 분리 저장한다.
`turn_paralinguistic` 또는 turns nullable 확장
barge-in 케이스가 audit 가능하게 남음
캐스케이드는 1차로 확정하고, s2s는 transcript 보존/몰입감 기준으로 별도 판단한다.
캐스케이드 vs s2s 비교 메모
윤찬 결정 항목으로 기록, 람다 단독 폐기 금지
IRB 승인 게이트가 아니라 교육 활용 파일럿과 데이터/개인정보 게이트로 관리한다.
수련생이 실제 계정으로 세션을 완주하고 사전/사후 설문을 남긴다.
참여자별 session, pre/post, consent state
20명 완주율, 중도중단 사유, 세션 로그 대조
SUS, 자기효능감, κ/ICC, 환각률, 피드백 타당도를 파일럿에서 처음 측정한다.
KPI report, metric query, anonymized export
SUS≥80, κ≥0.6/ICC≥0.75 같은 기준 충족 여부
라벨링/검수/κ 누적은 영속 워크플로우로 관리하고 fine-tuning 여부는 별도 결정한다.
export_manifest, JSONL, annotation checkpoint
train/test 누수 없음, 원문/가명본 경계 확인
미성년 원본 활용동의, 가명처리, 해외 전송 고지, 삭제/철회 절차를 실제 운영 기준으로 점검한다.
privacy checklist, audit log, withdrawal runbook
동의 철회 후 접근/학습 사용 차단 재현
실제 운영 도메인에 반영됐는지, 서버가 하나로 정리됐는지, 완료라고 말할 증거가 있는지 확인하는 탭이다.
커스텀 도메인이 최신 JS/CSS asset을 서빙한다.
`index-D1Z5DH5J.js`, `index-BA9-xxfn.css`
`Invoke-WebRequest https://vignette.chanpaca.net/`
터널은 prod API 8001을 보고, API는 DB와 engine을 모두 확인한다.
`environment:"prod"`, `db:true`, `engine:true`
`https://api-vignette.chanpaca.net/health`
운영 확인용 프로세스만 유지하고 임시 dev server는 내린다.
8001 API, 9099 engine, 20241 cloudflared
`Get-NetTCPConnection` 대상 포트 확인
Windows 재부팅/업데이트 후에는 engine gateway와 prod API, cloudflared를 같이 올려야 한다.
runbook 또는 scheduled task
재기동 후 public health와 `/turn` smoke 재확인
요약하지 않고 실행 범위, 명령, 결과를 행 단위로 남긴다.
| 범위 | 명령 / 확인 대상 | 결과 |
|---|---|---|
| Web typecheck | npm run typecheck | Passed |
| Backend unittest | python -m unittest app.test_runtime_policy app.test_session_turn_persistence engine_gateway.test_gateway_model | 20 tests OK |
| Python compile | python -m compileall app engine_gateway | Passed |
| Web build | npm run build | Passed |
| Pages production deploy | wrangler pages deploy dist --project-name vignette --branch main | `0fe8f3cc`, source `543d48c` |
| Custom domain assets | https://vignette.chanpaca.net/ | `index-D1Z5DH5J.js`, `index-BA9-xxfn.css` |
| Legacy Live2D routes | /live2d/mao/* / /live2d/haru/* / /live2d/live2dcubismcore.min.js | 404 |
| Compose | docker compose --env-file .env.example config --quiet | Passed with dummy required env |
| Focused E2E | admin + db-persistence + voice-success | 10 passed |
| Admin manage-users | admin.spec.ts --grep manage real server-known users | desktop/mobile 2 passed |
| Full E2E | PLAYWRIGHT_HOST=127.0.0.1 npx playwright test | 72 passed |
| Public auth discovery | E2E_PUBLIC_AUTH=1 npx playwright test --list --project=chromium-public-auth | 2 tests listed |
| Public readiness | public-auth-turn.spec.ts --grep production-safe | 1 passed |
| Persona auth boundary | GET /personas | verified local/public unauth 401 |
| Public login | auth.spec.ts --grep public login | 1 passed |
| Public runtime script | scripts\start-public-runtime.ps1 | engine 9099 + API 8001 + cloudflared only |
| Public API health | https://api-vignette.chanpaca.net/health | prod, db true, engine true |
| Local 5175 login | PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 auth.spec.ts | desktop/mobile passed |
| Learner/readiness E2E | learner.spec.ts + readiness.spec.ts desktop/mobile | 14 passed |
| Learner screenshots | learn-empty-desktop.png / learn-empty-mobile-compact.png | document overflow 0, empty history visible |
| Session layout/turn | session-layout.spec.ts desktop/mobile | 8 passed |
| Voice/session focused | voice + voice-success + session-layout | 15 passed |
| Latest focused E2E | learner + session-layout + settings + admin | 37 passed |
남은 public OAuth `/turn` proof 확보용 명령이다.
cd D:\workspace\vignette\apps\web npx playwright codegen https://vignette.chanpaca.net/login --save-storage=.\node_modules\.tmp\public-auth.json $env:E2E_PUBLIC_AUTH="1" $env:E2E_PUBLIC_STORAGE_STATE=".\node_modules\.tmp\public-auth.json" .\node_modules\.bin\playwright.cmd test e2e/public-auth-turn.spec.ts --project=chromium-public-auth
cd D:\workspace\vignette powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1
람다가 단독으로 정하면 월권인 항목이다. 결정되기 전에는 “완료”가 아니라 조건부로 둔다.
캐스케이드 1차는 확정. s2s는 몰입감과 transcript 보존 기준으로 유지/폐기를 정한다.
캐스케이드만 / s2s 2차 PoC 유지
음성 아키텍처, 전송 비용, 로그 보존 방식
로컬 상주 분류기를 기본으로 하되, 외부 Haiku/Solar API 폴백을 허용할지 정한다.
로컬 전용 / 외부 폴백 허용
데이터주권, 레이턴시, 장애 대응
1차는 few-shot 자동갱신만 할지, fine-tuning까지 포함할지 결정한다.
few-shot only / fine-tuning 포함
동의서, 데이터셋 품질, 운영 리스크
수정안은 기본 ON + 피드백 권장 배지 + 교수자 토글을 제안한다.
기본 ON / 교수자 승인 후 ON
학습자 자율성, 평가 품질, 안전 정책
n=1 시드의 한계를 해소하려면 추가 사례 수급 규모와 일정을 정해야 한다.
N건 목표/일정 확정
평가 타당도, 페르소나 다양성, Phase 3 측정력