음성 재생과 운영 배포 정리

This commit is contained in:
Yun Chan 2026-06-28 12:18:20 +09:00
parent 8ed185ce6c
commit ac7db95542
1020 changed files with 46863 additions and 2175 deletions

View file

@ -1,6 +1,6 @@
# Vignette Handoff
> Updated: 2026-06-27 19:10 KST. 새 세션은 이 문서와 `docs/DESIGN_CONCEPT.md`를 먼저 읽고 이어가면 된다.
> Updated: 2026-06-28 11:05 KST. 새 세션은 이 문서와 `docs/DESIGN_CONCEPT.md`를 먼저 읽고 이어가면 된다.
## 현재 상태
@ -11,7 +11,7 @@
- 공개 웹: `https://vignette.chanpaca.net`
- 공개 API: `https://api-vignette.chanpaca.net`
- 최신 앱 배포 소스 commit: `0ec266a` (`동의 게이트와 런타임 안정화`)
- 최신 Cloudflare Pages production deploy: `12da49f3`, source `0ec266a`, branch `main`.
- 최신 Cloudflare Pages production deploy: `24c90784`, branch `main`, dirty local source deploy.
- Google OAuth 허용 이메일 도메인: `hs.ac.kr`, `twentyoz.kr`
- 최신 백엔드 회귀: `python -m pytest app/ -q``130 passed`
- X1 재귀학습 export 1차: `scripts/export-recursive-dataset.py` 기본 read-only dry-run, `--write-dataset` 명시 시에만 `ds.*` write, approved export는 steward/legal/IAA gate 없으면 거부.
@ -19,22 +19,24 @@
- C3 이론모드 1차: `theory_mode`가 세션·평가·생성 프롬프트까지 흐르고, `build_turn_messages`는 인간중심/CBT/통합 프레이밍을 엔진 메시지에 넣는다. CBT 체인·이론부합 루브릭·명시적 선택 UI는 후속.
- H2 평가 정규화: 턴 평가의 대안발화도 `app.alternative_utterance`에 적재하고 리뷰 hydrate 시 `alternative_utterances`로 복원한다.
- X2 예산 경고/저비용 평가 라우팅: `ADMIN_USAGE_BUDGET_USD`가 0보다 크면 `/admin/usage`가 budget 상태(ok/warn/exceeded)를 반환하고 `/admin`이 예산 배너를 표시한다. `EVALUATOR_FAST_MODEL`/`EVALUATOR_DEEP_MODEL`을 설정하면 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달한다. 비우면 기존 gateway default 라우팅을 유지한다.
- 2026-06-28 11:17 KST: `/admin/usage` prod 503 원인은 provider/model breakdown 쿼리의 `ORDER BY tokens_in + tokens_out`가 집계 alias가 아니라 원본 컬럼 참조로 해석된 PostgreSQL `GroupingError`였다. `SUM(tokens_in)+SUM(tokens_out)` 집계식 정렬로 수정했고, public API 프로세스 `127.0.0.1:8001`의 인증 HTTP smoke에서 `source=database`, `durable=true`, 200을 확인했다.
- H4 LLM call audit + 동의 게이트: 상담 생성(generate/stream)과 fast/deep 평가의 외부 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행 증가했다. 이번 패스에서 `app_user.consent_at` 기반 learner 동의 수락/철회 API와 세션 시작/voice dev persona 시작 하드게이트를 추가했다. 한국어 이름/기관 NER와 guardian/legal 서명 evidence는 후속.
- RAG warm 동시성: E2E가 여러 세션을 빠르게 만들 때 BGE-M3 embedder가 동시에 지연 로드되어 `tqdm` lock 예외와 API health/dev-login timeout이 반복됐다. `rag.py` embedder load/encode를 process-wide `RLock`으로 직렬화하고, `sessions.py` warm task를 semaphore 1개로 제한했다. 최신 검증: `layout-visual-gate + session-layout` 11 passed.
- 계약 SSOT 4차: `apps/web/src/lib/api.ts`의 수기 DTO 중 auth, persona, admin/user/engine, review leaf, teacher safety/growth leaf, learner sessions, session review/worksheet aggregate, teacher dashboard aggregate에 이어 `SessionStartResponse`, `SessionDetailResponse`, `SessionDetailTurn`까지 `apps/web/src/lib/api.gen.ts``ApiSchema` alias로 전환했다. 백엔드 세션 응답 stage는 `StageLabel` enum(`라포|탐색|개입|정리`)으로 고정했고, generated optional/default 차이는 화면 form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. `.github/workflows/api-contract.yml`은 PR/master push에서 `npm run check:api-types`로 FastAPI OpenAPI와 generated type 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/session-review/teacher/layout focused E2E 32 passed.
- 턴 런타임 리팩터 2차: `turn_runtime.finalize_completed_turn`으로 REST submit/stream/voice WS의 `record_completed_turn` + `record_safety_event` 호출쌍을 공통화했다. gateway `/v1/stream` route 레벨에서 `token/done/error` SSE 프레임 contract tests를 추가했다. 검증: focused backend 40 passed.
- Phase3 artifact checker 강화: `scripts/check-phase3-artifacts.py`가 CSV enum, KPI report 필수 field, approved export의 PII pass·κ/ICC·withdrawn exclusion·consent scope·file sha256을 검증한다. `app/test_phase3_artifact_checker.py` 5 tests 추가. 실제 파일럿 evidence와 steward/legal/IAA gate는 그대로 외부 의존이다.
- 세션 종료 UX/dark theme SSOT 정리: 드래그형 `SlideToEnd`를 명시 확인 다이얼로그로 교체하고, Topbar/Settings theme 토글을 `lib/theme.ts` 단일 경로로 통합했다. 저장값이 없으면 dark를 기본으로 두고 앱 부팅 시 theme를 먼저 적용해 초기 flash를 줄인다. API 기본 preference `system`은 Settings에서 light로 오해하지 않고 현재 초기 테마를 따른다. 회기 리뷰·설정 포함 주요 페이지의 흰 섹션 잔재도 dark 작업면으로 맞췄다. 검증: `npm run typecheck`, `npm run build`, `npx playwright test e2e/layout-visual-gate.spec.ts e2e/settings.spec.ts e2e/session-review.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run --workers=1` 22 passed.
- PSD v2 기반 P1 아바타 보정: P1 기본 아바타는 컨셉 보드 크롭이 아니라 사용자 제공 `라투디 여캐_ver2.psd`에서 추출한 `seoyeon-live2d-psd-v2`를 사용한다. `sad` 표정은 PSD의 울상 눈썹, 우는 입, 눈물 레이어를 별도 파츠로 합성한다. active session 데스크톱 스테이지는 `상태 라벨 / 아바타 / 현재 상태` 3행 구조를 유지한다. 재현 스크립트와 소스 복사본은 `docs/avatar-art/seoyeon/live2d-psd-v2/`, 앱 자산은 `apps/web/public/avatar/seoyeon-live2d-psd-v2/parts/`에 있다.
- M3 인증 claim 1차: Google/SAML/dev-login이 설정 기반 cohort map과 SAML cohort claim을 `cohort_ids`로 넘기고, 관리 사용자 `external_id`는 provider subject 기반(`google:`/`saml:`/`dev:`)으로 저장한다. 운영 SAML 서명검증·기관 claim schema·deprovisioning audit은 후속.
- 페르소나 저작/P4~P7 적재 2차: `data/personas/P4.json`~`P7.json`을 저장소 관리 `PersonaCard`로 읽어 `materialize_seed_personas()`와 seed fallback catalog에 포함했다. 검증: `python -m pytest app/test_persona_review.py -q` 22 passed, 로컬 dev DB materialize 결과 `approved_codes=P1,P2,P3,P4,P5,P6,P7`, 인증된 로컬 `GET /personas``P1..P7` 반환.
- Google OAuth 진단 1차: provider callback error는 `access_denied`/`provider_error`로 분리하고, 로그인 화면은 실패 reason code를 함께 표시한다. 실제 Google 계정 완료 proof는 아직 owner 로그인/storageState가 필요하다.
- `frontenddesign` 스킬은 현재 세션의 사용 가능 스킬 목록에 없었다. 대신 `docs/DESIGN_CONCEPT.md`를 SSOT로 사용했다.
- 공개 API 터널은 현재 `C:\Users\encep\.cloudflared\vignette-config.yml`에서 `http://127.0.0.1:8001`을 본다.
- 공개용 API 프로세스는 `127.0.0.1:8001`에서 `ENVIRONMENT=prod`로 떠 있다. 로컬 개발 API `127.0.0.1:8000`과 Tailnet 개발 API `127.0.0.1:8010``ENVIRONMENT=dev`로 떠 있다.
- 2026-06-27 05:34:32 UTC 모바일 502는 cloudflared 로그상 `127.0.0.1:8001` origin connection refused와 일치한다. 현재 public API는 복구됐고, watchdog 기본 검사에서 아직 DNS가 없는 `api-vnet.18ka.net`을 제외해 향후 설치 시 불필요한 restart loop를 막았다. 2026-06-27 18:25 KST 재확인: `https://vignette.chanpaca.net/login` 200, `https://api-vignette.chanpaca.net/health` prod/db/engine true, public Google start 302 to `accounts.google.com` + HttpOnly state cookie, provider callback error → `/login?oauth=access_denied`.
- 2026-06-27 05:34:32 UTC 모바일 502는 cloudflared 로그상 `127.0.0.1:8001` origin connection refused와 일치한다. 현재 public API는 복구됐고, watchdog 기본 검사에서 아직 DNS가 없는 `api-vnet.18ka.net`을 제외해 향후 설치 시 불필요한 restart loop를 막았다. 2026-06-28 11:05 KST 재확인: `https://vignette.chanpaca.net/login` 200 with `assets/index-HjXyC0JT.js` + `assets/index-C8JKXhNP.css`, `https://api-vignette.chanpaca.net/health` prod/db/engine true, public unauth `GET /personas` 401.
- `GET /personas`는 이제 인증 필요다. 검증 당시 로컬 `http://127.0.0.1:8000/personas`와 현재 공개 `https://api-vignette.chanpaca.net/personas` 모두 비로그인 401 확인 완료.
- 로컬 웹이 `http://127.0.0.1:5175`처럼 다른 Vite 포트로 떠도 로그인은 `로컬 테스트 계정으로 계속` 버튼을 사용한다. Google OAuth 버튼은 로컬에서는 disabled로 둔다. 현재 OAuth callback이 공개 API로 돌아가기 때문에 로컬 Google OAuth는 로컬 세션에 붙을 수 없다.
- 현재 확인용 프로세스는 `127.0.0.1:9099` engine gateway, `127.0.0.1:8001` prod public API, `127.0.0.1:8000` dev API, `127.0.0.1:8010` Tailnet dev API, `127.0.0.1:5173` Vite web, `cloudflared` tunnel 1개다. Tailnet URL은 `https://alpaca-home.taile93291.ts.net/login`이다. stale Vite allowedHosts로 403이 났던 상태는 재검증 시 `login` 200, `/api/health` dev/db/engine true, `/api/auth/config` 200이었다. `alpaca-home.taile93291.ts.net`는 Vite 기본 `allowedHosts`에도 포함했다.
- 현재 확인용 프로세스는 `127.0.0.1:9099` engine gateway, `127.0.0.1:8001` prod public API, `127.0.0.1:8000` dev API, `127.0.0.1:8010` Tailnet dev API, `127.0.0.1:5173` Vite dev web, `127.0.0.1:5174` public preview, `cloudflared` tunnel 1개다. Tailnet URL은 `https://alpaca-home.taile93291.ts.net/login`이다. stale Vite allowedHosts로 403이 났던 상태는 재검증 시 `login` 200, `/api/health` dev/db/engine true, `/api/auth/config` 200이었다. `alpaca-home.taile93291.ts.net`는 Vite 기본 `allowedHosts`에도 포함했다.
- `scripts/dev-up.ps1 -NoGateway -NoWeb`는 이제 gateway/web stale 정리를 건너뛰고 지정 `-ApiPort`의 API만 재기동한다. 검증 당시 8000 API-only 재기동 후 8001/8010/5173/9099/20241 listener가 보존됐다.
- Docker `vignette-dev-db`는 실행 중이고 DB는 accepting connections다. 단, 기존 컨테이너라 healthcheck가 없고 `POSTGRES_USER=vignette` 기반이다. `vignette_app` role은 NOBYPASSRLS로 존재하지만 현재 API startup DDL이 owner 권한을 요구하므로 런타임 app-role 전환은 마이그레이션 owner/런타임 role 분리 후 진행한다.
- 공개 런타임 재기동 스크립트:
@ -129,6 +131,11 @@
- Web production build:
- `cd apps/web; fnm use 22.22.3; npm run build`
- Passed
- Design/theme verification:
- `docs/design-verification/full-pages/01..07-*-{desktop,tablet,mobile}.png`
- 7개 주요 화면 x 3뷰포트 = 21장 캡처 갱신. 자동 검사 결과: horizontal overflow 0, 큰 흰 CSS 배경면 0. 세션 active는 `seoyeon-live2d-psd-v2` 래스터 로드, 현재 상태 배지와 아바타 겹침 없음.
- `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`
- Docker compose config:
- With dummy required env vars, `docker compose -f infra\docker-compose.yml --env-file infra\.env.example config --quiet`
- Passed
@ -162,8 +169,8 @@
- API 8001 health and public `https://api-vignette.chanpaca.net/health` both returned `environment:"prod"`, `db:true`, `engine:true`
- Public web deployment:
- `wrangler pages deploy dist --project-name vignette --branch main --commit-dirty=true`
- Production deployment `12da49f3`
- `https://vignette.chanpaca.net/` serves `assets/index-DEgzFAUm.js` and `assets/index-BGRtWhu_.css`
- Production deployment `24c90784`
- `https://vignette.chanpaca.net/` serves `assets/index-HjXyC0JT.js` and `assets/index-C8JKXhNP.css`
- `https://vignette.chanpaca.net/live2d/mao/Mao.model3.json`, Haru model, and Cubism core routes return 404
- Public login screen:
- `npx playwright test e2e/auth.spec.ts --project=chromium-desktop --grep "public login"`
@ -205,7 +212,7 @@
- 2026-06-26 15:18 KST 기준 `apps/web/node_modules/.tmp/public-auth.json`은 없다. codegen 창/프로세스가 남아 있으면 로그인 완료 후 창을 닫아 저장을 완료하면 된다.
- 공개 API `environment:"dev"` 문제는 이번 세션에서 해결했다. cloudflared가 prod API인 `127.0.0.1:8001`을 보도록 변경했고 public gate로 확인했다.
- Google Console 설정은 브라우저 로그인 세션이 필요하다. 막히면 사용자가 로그인해 주기로 했다.
- 현재 worktree에는 별도 아바타/세션 화면 미커밋 변경이 남아 있을 수 있다(`Session.tsx`, `session.css`, `apps/web/public/avatar/p1-concept/`). 관련 없는 사용자 변경을 되돌리거나 다른 커밋에 섞지 말 것.
- 현재 worktree에는 이번 디자인 패스의 아바타/세션 화면 변경이 남아 있다(`Session.tsx`, `session.css`, `apps/web/public/avatar/seoyeon-live2d-psd-v2/`, `avatar-expression.spec.ts`, 관련 문서). 과거 임시 크롭 `apps/web/public/avatar/p1-concept/`는 P1 기본값으로 쓰지 않는다. API 쪽 변경(`apps/api/app/persona_repository.py`, `apps/api/app/test_persona_review.py`)은 이번 디자인 패스 범위에서 수정하지 않았으므로 별도 변경으로 취급하고 되돌리지 말 것.
## 다음 세션 우선순위