로컬 회귀와 공개 prod-safe 게이트 통과
-백엔드 unittest 20개, Python compile, 웹 production build, 전체 Playwright 72개가 통과했다. 공개 API도 `environment:"prod"`, `db:true`, `engine:true`로 응답한다.
-diff --git a/docs/dev_dashboard.html b/docs/dev_dashboard.html index da49fa6..bce69dd 100644 --- a/docs/dev_dashboard.html +++ b/docs/dev_dashboard.html @@ -135,6 +135,55 @@ color:var(--ink-2); font-size:13px; } + .tabs { + border:1px solid var(--hair); + border-radius:12px; + background:var(--surface); + overflow:hidden; + box-shadow:0 1px 2px rgba(28,42,42,.05); + } + .tabbar { + display:flex; + gap:0; + overflow-x:auto; + border-bottom:1px solid var(--hair); + background:var(--paper-2); + } + .tab-button { + appearance:none; + border:0; + border-right:1px solid var(--hair); + background:transparent; + color:var(--ink-2); + padding:13px 16px; + font:700 13px/1.2 var(--font); + white-space:nowrap; + cursor:pointer; + } + .tab-button:hover { background:rgba(62,122,110,.08); color:var(--ink); } + .tab-button[aria-selected="true"] { background:var(--surface); color:var(--accent-deep); box-shadow:inset 0 -2px 0 var(--accent); } + .tab-panel { padding:20px; } + .tab-panel[hidden] { display:none; } + .detail-head { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:18px; align-items:start; margin-bottom:16px; } + .detail-head h3 { margin:0; font-size:18px; line-height:1.35; } + .detail-head p { margin:6px 0 0; color:var(--ink-2); font-size:13px; } + .mini-metrics { display:flex; flex-wrap:wrap; gap:8px; justify-content:flex-end; } + .mini-metrics span { display:inline-flex; align-items:center; min-height:28px; padding:4px 9px; border:1px solid var(--hair); border-radius:999px; background:var(--paper); color:var(--ink-2); font:650 12px/1 var(--num); } + .task-list { display:grid; border:1px solid var(--hair); border-radius:12px; overflow:hidden; background:var(--hair); gap:1px; } + .task-row { display:grid; grid-template-columns:108px minmax(220px,1fr) minmax(180px,.8fr) minmax(190px,.9fr); gap:0; background:var(--surface); } + .task-row > div { padding:13px 14px; min-width:0; } + .task-row b { display:block; margin-bottom:4px; font-size:13px; color:var(--ink); } + .task-row p { margin:0; color:var(--ink-2); font-size:12px; line-height:1.45; } + .task-status { display:inline-flex; align-items:center; justify-content:center; min-width:76px; min-height:28px; padding:4px 8px; border-radius:999px; font:760 12px/1 var(--num); } + .s-done { background:var(--pos-bg); color:var(--pos); } + .s-doing { background:var(--warn-bg); color:var(--warn); } + .s-risk { background:var(--crit-bg); color:var(--crit); } + .s-plan { background:var(--info-bg); color:#2E5A7A; } + .detail-blocks { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px; margin-top:14px; } + .detail-block { border:1px solid var(--hair); border-radius:12px; padding:14px; background:var(--paper); } + .detail-block h4 { margin:0 0 8px; font-size:13px; } + .detail-block ul { margin:0; padding-left:18px; color:var(--ink-2); font-size:12px; line-height:1.6; } + .detail-block li + li { margin-top:4px; } code { font-family:var(--num); font-size:12px; background:var(--paper-2); padding:2px 6px; border-radius:6px; } .wide { grid-column:1 / -1; } .commands { display:grid; gap:10px; } @@ -149,6 +198,12 @@ .hero { grid-template-columns:1fr; } .grid { grid-template-columns:1fr; } .roadmap { grid-template-columns:1fr; } + .detail-head { grid-template-columns:1fr; } + .mini-metrics { justify-content:flex-start; } + .task-row { grid-template-columns:1fr; } + .task-row > div { padding:12px 14px; border-bottom:1px solid var(--hair); } + .task-row > div:last-child { border-bottom:0; } + .detail-blocks { grid-template-columns:1fr; } } @media (max-width:520px) { .summary { grid-template-columns:1fr; } @@ -160,7 +215,7 @@
이 HTML만 보고 판단해야 하는 항목
+탭별 할 일, 산출물, 검증 기준. 팀장 리뷰는 여기서 다음 작업을 고르면 된다.
백엔드 unittest 20개, Python compile, 웹 production build, 전체 Playwright 72개가 통과했다. 공개 API도 `environment:"prod"`, `db:true`, `engine:true`로 응답한다.
-Cloudflare Pages production `0fe8f3cc`이 `543d48c`로 배포됐고, `https://vignette.chanpaca.net`이 `index-D1Z5DH5J.js`와 `index-BA9-xxfn.css`를 서빙한다.
-실제 Google 로그인 쿠키가 필요한 공개 `/turn` smoke는 아직 storageState가 없어 미완이다. 로그인 가능한 계정으로 한 번 캡처하면 바로 검증 가능하다.
-프론트 페이지는 실제 API/DB 경로를 탄다. 남은 `mock` 표기는 테스트 코드의 `unittest.mock` 또는 의도적 fixture이며, seed fallback은 prod/staging에서 fail-closed 된다.
-지금 단계는 구현을 더 벌리는 단계가 아니라, 운영 증거를 닫고 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 게이트로 문서 전수 정합
목표는 “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 재확인
람다가 단독으로 정하면 월권인 항목이다. 결정되기 전에는 “완료”가 아니라 조건부로 둔다.
+캐스케이드 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 측정력
새 세션에서 반복하지 않아도 되는 작업
-`session-persistence`, `teacher`, `voice`는 `/api/personas`에서 실제 DB persona를 가져온다.
-prod/staging에서 dev login, seed fallback, OAuth 누락, 기본 secret, localhost CORS를 거부한다.
-`ENVIRONMENT`, Google OAuth vars, `CORS_ORIGINS`, `OPENAI_BASE_URL`을 API 컨테이너에 명시했다.
-긴 readiness detail을 안정화했고, 사용자 변경은 DB 저장소가 durable할 때만 허용한다. AI 운영은 실제 어댑터명과 health 상태를 표시한다.
-`db-persistence`는 전역 engine config를 바꾸므로 `@single-run`으로 분리했다.
-`docs/HANDOFF.md`, `docs/dev_dashboard.html`, `apps/web/e2e/README.md`를 최신 상태로 맞췄다.
-`cloudflared` ingress를 `127.0.0.1:8001` prod API로 돌렸고 public readiness gate가 통과했다.
-`scripts/start-public-runtime.ps1`와 `scripts/stop-public-runtime.ps1`로 API 8001과 cloudflared를 재현 가능하게 만들었다.
-`/personas`는 이제 비로그인 401이다. 로컬과 공개 API 모두 직접 확인했고 E2E 회귀를 추가했다.
-public 웹에서 `/api/auth/config`가 SPA HTML로 떨어지지 않도록 public API origin fallback을 추가했다.
-학습자 홈은 이제 fake 기록 없이 실제 서버 세션 0개를 `기존 회기` 빈 상태와 0 카운트로 표시한다.
-`127.0.0.1:5175` web, `8000` API, `9099` engine을 재기동하고 dev-login 서버 세션과 `/learn` redirect를 확인했다.
-375px급 active session에서 stage 내부가 1px 넘게 잘리던 문제를 avatar/padding 조정으로 제거했다.
-`POST /sessions/{id}/stream` token 이벤트로만 내담자 자막을 쌓고, HTTP 거절과 SSE error 모두 실패 턴을 transcript와 저장소에 남기지 않는다.
-음성 회기도 엔진 응답 생성 성공 후에만 learner/client turn을 저장한다. 실패한 transcript는 pending 상태에서 제거한다.
-admin health와 voice degraded reason은 내부 fallback/seed 표현 대신 비영구 런타임 상태를 사용자용 문구로 표시한다.
-`/learn/session/:code`로 직접 들어와도 DB 원본 persona가 아니면 회기 시작을 막는다. degraded/seed fallback 회귀를 추가했다.
-Mao/Haru 샘플과 Pixi/Cubism 런타임을 공개 배포물에서 제거했다. 세션 화면은 SVG persona avatar만 렌더링한다.
-마지막으로 직접 실행한 결과
-| 범위 | 명령 | 결과 |
|---|---|---|
| Web typecheck | npm run typecheck | Passed |
| Backend | python -m unittest app.test_runtime_policy app.test_session_turn_persistence engine_gateway.test_gateway_model | 20 tests OK |
| 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 |
다음 세션 첫 작업
-2026-06-26 15:18 KST 기준 Google 로그인 storageState가 없어 공개 API `/turn` 실제 성공 증거가 아직 없다.
-현재 실행 프로세스는 `127.0.0.1:9099` engine gateway, `127.0.0.1:8001` prod API, cloudflared tunnel 1개다. 재부팅 후에는 engine gateway와 prod API, cloudflared를 같이 재기동해야 한다.
-E2E가 admin engine config를 건드린다. 배포 전 의도한 model/url/mode인지 다시 확인해야 한다.
-공개 OAuth smoke
-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-
작업 방식
-