동의 게이트와 런타임 안정화
This commit is contained in:
parent
0eb7d925ed
commit
0ec266a761
34 changed files with 1186 additions and 158 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Vignette Handoff
|
||||
|
||||
> Updated: 2026-06-27 16:16 KST. 새 세션은 이 문서와 `docs/DESIGN_CONCEPT.md`를 먼저 읽고 이어가면 된다.
|
||||
> Updated: 2026-06-27 17:06 KST. 새 세션은 이 문서와 `docs/DESIGN_CONCEPT.md`를 먼저 읽고 이어가면 된다.
|
||||
|
||||
## 현재 상태
|
||||
|
||||
|
|
@ -10,25 +10,26 @@
|
|||
- 로컬 engine gateway 기본 포트: `http://127.0.0.1:9099`
|
||||
- 공개 웹: `https://vignette.chanpaca.net`
|
||||
- 공개 API: `https://api-vignette.chanpaca.net`
|
||||
- 앱 배포 소스 commit: `6bd91b0d` (`현재 작업 상태 저장`)
|
||||
- 최신 Cloudflare Pages production deploy: `2a58ca49`, source `6bd91b0d` + dirty worktree, branch `main`
|
||||
- 직전 기록된 앱 배포 소스 commit: `6bd91b0d` (`현재 작업 상태 저장`)
|
||||
- 직전 기록된 Cloudflare Pages production deploy: `2a58ca49`, source `6bd91b0d` + dirty worktree, branch `main`. 최신 배포 ID는 `wrangler pages deploy` 실행 로그와 최종 작업 보고를 기준으로 확인한다.
|
||||
- Google OAuth 허용 이메일 도메인: `hs.ac.kr`, `twentyoz.kr`
|
||||
- 최신 백엔드 회귀: `python -m pytest app/ -q` → `122 passed`
|
||||
- 최신 백엔드 회귀: `python -m pytest app/ -q` → `125 passed`
|
||||
- X1 재귀학습 export 1차: `scripts/export-recursive-dataset.py` 기본 read-only dry-run, `--write-dataset` 명시 시에만 `ds.*` write, approved export는 steward/legal/IAA gate 없으면 거부.
|
||||
- C1 사례개념화 워크시트 1차: `SessionReviewResponse.caseWorksheet`와 리뷰 화면 read-only 카드가 축어록 근거 기반 초안을 제공한다. 편집·DB 저장, 임상 루브릭, AI 추출/채점은 후속.
|
||||
- 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 라우팅을 유지한다.
|
||||
- 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행 증가했다.
|
||||
- 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.
|
||||
- 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은 후속.
|
||||
- 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 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 17:00 KST 재확인: `https://vignette.chanpaca.net/login` 200, `https://api-vignette.chanpaca.net/health` prod/db/engine true.
|
||||
- `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`이며 dev-login까지 검증됐다.
|
||||
- 현재 확인용 프로세스는 `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이 났던 상태는 `scripts/start-tailscale-runtime.ps1` 재기동 후 `login` 200, `/api/health` dev/db/engine true, direct Google start → `local_oauth_unavailable`로 재검증됐다.
|
||||
- `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 분리 후 진행한다.
|
||||
- 공개 런타임 재기동 스크립트:
|
||||
|
|
|
|||
|
|
@ -450,6 +450,9 @@
|
|||
<p class="dg-note">2차 적용(2026-06-27): 생성된 관리자/설정/리뷰 시안 기준으로 admin 역할의 상단바·사이드바를 어두운 운영 콘솔 크롬으로 전환했고, 관리자 사용자 관리 행을 조밀한 운영 테이블 밀도로 낮췄다. 회기 리뷰는 헤더·인사이트 카드·피드백 스테이지에 따뜻한 래스터 질감과 낮은 채도 틴트를 적용했다.</p>
|
||||
<p class="dg-note">3차 적용(2026-06-27): 상담 세션 시작/진행 화면을 시안 기준의 어두운 상담 스테이지로 재정렬했다. 시작 전 화면은 내담자 무대·브리핑·진행 초점 3영역으로 유지하고, 진행 중 화면은 어두운 캔버스 위에 중앙 스테이지와 자막/컨트롤을 배치한다.</p>
|
||||
<p class="dg-note">4차 적용(2026-06-27): 교수 콘솔은 시안의 triage 콘솔 구조에 맞춰 KPI를 전폭 6칸으로 재배치하고, 종료 회기 검토 큐와 최근 회기 기록을 첫 화면 2열 핵심 영역으로 올렸다. 성장 추적·페르소나 저작·검수 보조 큐는 하단 보조 업무로 낮췄고, 교수자 사이드바는 좁은 teal rail로 전환했다.</p>
|
||||
<p class="dg-note">5차 적용(2026-06-27): 회기 리뷰는 시안의 좌·중·우 작업대 구조에 맞춰 요약/액션 레일, 감정·흐름 중앙 영역, 우측 인사이트 패널로 재배치했다. 모바일에서는 트랜스크립트를 워크시트보다 먼저 노출하고, 사례개념화 워크시트는 제한 높이의 보조 패널로 낮춰 긴 문서처럼 보이지 않게 했다.</p>
|
||||
<p class="dg-note">6차 적용(2026-06-27): 설정 화면은 글로벌 셸을 숨기고 설정 전용 좌측 레일을 주 내비로 승격했다. 계정, AI 운영, 테마, 음성, 알림 카드를 시안의 차분한 설정 워크스페이스 흐름에 맞춰 단일 컬럼으로 재배치했고, 모바일은 상단 고정 섹션 탭으로 모든 항목이 잘리지 않게 정리했다.</p>
|
||||
<p class="dg-note">7차 적용(2026-06-27): 로그인 화면은 별도 생성한 래스터 상담실 배경(<code>login-counseling-room.png</code>)을 왼쪽 브랜드 패널에 적용해 시안의 실제 상담실 분위기를 반영했다. 학습자 홈은 모바일 grid 행 계산 때문에 생기던 리스트-상세 간 큰 공백을 제거하고, 회기 준비 보드가 한 흐름으로 이어지게 정리했다.</p>
|
||||
<div class="dg-principles" aria-label="디자인 생성 가드레일">
|
||||
<div><b>래스터만 사용</b><span>이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.</span></div>
|
||||
<div><b>기능 우선</b><span>메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.</span></div>
|
||||
|
|
@ -592,8 +595,8 @@
|
|||
<div class="scard-body"><div class="kv"><b>현재</b><p><code>POST/GET/PUT /personas/drafts</code>를 추가해 teacher/admin이 페르소나 카드를 draft로 생성·조회·편집하고 review 상태로 올릴 수 있다. 교수 콘솔에는 JSON 초안 패널과 검수 큐의 편집 불러오기를 연결했다. 승인된 카드만 학습자 catalog에 노출되는 기존 경계는 유지.</p></div><div class="kv"><b>검증</b><p><code>pytest app/test_persona_review.py -q</code> 19 passed, <code>pytest app/ -q</code> 119 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code> passed.</p></div><div class="kv k-warn"><b>잔여</b><p>P4~P7 실제 임상 콘텐츠 적재, JSON 대신 항목형 저작 UI, 루브릭·이론 콘텐츠 외부화는 후속. 임상팀이 제공하지 않은 페르소나 내용을 임의 생성하지 않는다.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="done" data-cat="보안·PII" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">H4 부분 DONE</span><span class="scard-mid"><span class="scard-title">PII 마스킹 + LLM 호출 감사</span><span class="scard-sum">날짜·금액·주소 정규식, metadata-only llm_call_log 적재. 전체 API 122.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>정규식 폴백에 한국어 고위험 패턴 추가: 구체 날짜(2001.4.18→[DATE])·금액(1200원→[MONEY])·행정구역 주소(서울시 강남구 역삼동→[ADDR]). 상담 생성(generate/stream)·fast/deep 평가의 외부 LLM 호출 직후 <code>audit.llm_call_log</code>에 provider/model/token/cost/inference_geo/latency만 적재한다. prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 <code>/turn</code> smoke에서 <code>audit.llm_call_log</code>가 8→11로 3행 증가했다. 검증: 마스킹/감사 5 passed, 관련 회귀 27 passed, 전체 API 122 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>이름/기관명은 NER 필요(Presidio ko 모델·spaCy ko, 무거움 — TODO 명시), 미성년/guardian 동의 수집·게이트·철회. 공개 Google OAuth 실제 <code>/turn</code> proof는 별도 운영 게이트.</p></div></div>
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">H4 부분 DONE</span><span class="scard-mid"><span class="scard-title">PII 마스킹 + LLM 호출 감사 + 동의 게이트</span><span class="scard-sum">metadata-only llm_call_log, learner consent_at 하드게이트. 전체 API 125.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>정규식 폴백에 한국어 고위험 패턴 추가: 구체 날짜(2001.4.18→[DATE])·금액(1200원→[MONEY])·행정구역 주소(서울시 강남구 역삼동→[ADDR]). 상담 생성(generate/stream)·fast/deep 평가의 외부 LLM 호출 직후 <code>audit.llm_call_log</code>에 provider/model/token/cost/inference_geo/latency만 적재한다. prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 <code>/turn</code> smoke에서 <code>audit.llm_call_log</code>가 8→11로 3행 증가했다. 이번 패스에서 <code>app_user.consent_at</code> 기반 learner 전용 동의 수락/철회 API와 회기 시작·voice dev persona 시작 하드게이트를 추가했고, 세션 시작 전 프론트 동의 UI와 E2E seed를 연결했다. 검증: 동의/세션/음성 회귀 64 passed, 전체 API 125 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>.</p></div><div class="kv k-warn"><b>잔여</b><p>이름/기관명은 NER 필요(Presidio ko 모델·spaCy ko, 무거움 — TODO 명시). 미성년/guardian 및 법무 검토가 필요한 서명 동의서·개인정보 고지 evidence는 외부 게이트다. 공개 Google OAuth 실제 <code>/turn</code> proof는 별도 운영 게이트.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="planned" data-cat="리팩터·정리" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-plan">MED M1~M3</span><span class="scard-mid"><span class="scard-title">비언어 임상이벤트(M1)·다회기 케이스 아크(M2)·SSO claim/식별자/감사(M3)</span><span class="scard-sum">M1/M2 1차 완료. M3는 cohort map + provider external_id 1차 완료, 기관 SAML/거버넌스는 남음.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
|
|
@ -605,7 +608,7 @@
|
|||
</article>
|
||||
</div>
|
||||
<div class="detail-blocks">
|
||||
<div class="detail-block"><h4>즉시 착수 가능(내부 코드, 외부 합의 불요)</h4><ul><li>C1/C2/C3/H2/H3은 1차 구조 완료. 후속은 저장형 워크시트, 임상 스크립트, CBT 체인, 2열 리뷰 UI, P4~P7 콘텐츠 적재처럼 데이터·정책·UI 고도화 영역이다.</li><li>H4(부분): 한국어 정규식 마스킹, metadata-only LLM call audit 경로, 로컬 live row proof는 완료. 남은 내부 후보는 이름/기관 NER와 동의 게이트 골격이다. X1 dry-run export 1차와 X2 비용 관측·예산 경고/evaluator 모델 override 1차는 완료, X2 semantic cache는 후속. M1/M2/M3 1차는 완료, provider 기반 한숨·울음 감지와 case_digest/pinned_fact 실적재, 운영 SAML 서명검증은 후속.</li></ul></div>
|
||||
<div class="detail-block"><h4>즉시 착수 가능(내부 코드, 외부 합의 불요)</h4><ul><li>C1/C2/C3/H2/H3은 1차 구조 완료. 후속은 저장형 워크시트, 임상 스크립트, CBT 체인, 2열 리뷰 UI, P4~P7 콘텐츠 적재처럼 데이터·정책·UI 고도화 영역이다.</li><li>H4(부분): 한국어 정규식 마스킹, metadata-only LLM call audit 경로, 로컬 live row proof, learner 동의 수락/철회/회기 시작 하드게이트 골격은 완료. 남은 내부 후보는 이름/기관 NER이고, guardian/legal 서명 동의 evidence는 외부 게이트다. X1 dry-run export 1차와 X2 비용 관측·예산 경고/evaluator 모델 override 1차는 완료, X2 semantic cache는 후속. M1/M2/M3 1차는 완료, provider 기반 한숨·울음 감지와 case_digest/pinned_fact 실적재, 운영 SAML 서명검증은 후속.</li></ul></div>
|
||||
<div class="detail-block"><h4>소유자 결정 / 외부(임상팀·기관) 의존</h4><ul><li>임상팀 산출물: C1 워크시트 항목·루브릭, C3 CBT 콘텐츠·체인, C2 위기 스크립트·서약 문안, H2 골든셋 코딩.</li><li>소유자 평가설계: H1 실험/통제군·3척도 문항·50분 흐름, κ/ICC·환각률 목표(doc4 미명시).</li><li>기관(한신 IT): M3 SSO IdP·test tenant·claim 스키마·인증서, 거버넌스 증거.</li><li>거버넌스: L1 스택 정합성 방향·저작권 등재 문서화·IP 협의.</li></ul></div>
|
||||
<div class="detail-block"><h4>판독 한계 (정직성)</h4><ul><li>핵심 축어록 2단 표는 PDF 시각 판독이 1차 근거(txt 일부 누락), OCR 잔재 존재.</li><li>doc4 κ/ICC·환각률은 신청서 본문 미명시 — 계약 확정 지표로 단정 금지.</li><li>'현재상태'는 grep/코드 사실 기반. C1~C3·H2·H3은 직접 재확인(✓), 그 외는 착수 전 코드 1차 재확인 권고.</li></ul></div>
|
||||
</div>
|
||||
|
|
@ -641,7 +644,7 @@
|
|||
</article>
|
||||
<article class="scard" data-status="done" data-cat="인프라·스택·RAG" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">RAG 활성화</span><span class="scard-mid"><span class="scard-title">"좋은 RAG" 미배선 → 라이브 활성화 + 성능 회귀 수정</span><span class="scard-sum">BGE-M3 핀+배선, 임베더 동기로드 블로킹을 백그라운드 task로. E2E 27/27·11/11·4/4.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>임베더 BGE-M3 검증+버전핀(1.4.0 dtype버그→1.2.11/transformers 4.44.2). <code>embed_query</code>를 asyncio.to_thread로 offload. 턴 파이프라인 배선: <code>retrieve_persona_memory</code>→episodic recall, <code>search_kb</code>→kb_behavior_cues. KB seed 3청크 적재. rag.py latent 버그 3건 수정. GraphRAG는 2차 보류.</p></div><div class="kv"><b>성능 회귀 수정</b><p>최초 RAG 배선이 회기 시작/턴에 임베더 로드(~7-13s)를 <b>동기 await</b>해 <code>POST /sessions</code>가 막힘 → session-layout E2E 4건 타임아웃. 수정: 회상·KB 회수를 <code>_warm_rag_caches</code> <b>백그라운드 task</b>로 옮기고 턴은 캐시만 읽음(비차단). 재검증: session-layout 4/4 + <b>핵심 E2E 27/27·11/11·아바타 4/4</b> 통과.</p></div><div class="kv"><b>시각 게이트</b><p>admin 1280 input "text-clipped-x" 1건은 폼 input 값 스크롤(설계상 정상) 오탐 — form 컨트롤을 text-clip 판정에서 제외 → <b>게이트 7/7</b>(실 결함 검사 유지).</p></div></div>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>임베더 BGE-M3 검증+버전핀(1.4.0 dtype버그→1.2.11/transformers 4.44.2). <code>embed_query</code>를 asyncio.to_thread로 offload. 턴 파이프라인 배선: <code>retrieve_persona_memory</code>→episodic recall, <code>search_kb</code>→kb_behavior_cues. KB seed 3청크 적재. rag.py latent 버그 3건 수정. GraphRAG는 2차 보류.</p></div><div class="kv"><b>성능 회귀 수정</b><p>최초 RAG 배선이 회기 시작/턴에 임베더 로드(~7-13s)를 <b>동기 await</b>해 <code>POST /sessions</code>가 막힘 → session-layout E2E 4건 타임아웃. 수정: 회상·KB 회수를 <code>_warm_rag_caches</code> <b>백그라운드 task</b>로 옮기고 턴은 캐시만 읽음(비차단). 이번 동의 게이트 E2E 재검증 중 여러 세션 warm task가 BGE-M3를 동시에 지연 로드해 API health/dev-login timeout이 반복된 문제는 embedder load/encode <code>RLock</code>과 warm semaphore 1개로 안정화했다. 재검증: session-layout 4/4 + <b>핵심 E2E 27/27·11/11·아바타 4/4</b>, 최신 layout/session 조합 <b>11/11</b> 통과.</p></div><div class="kv"><b>시각 게이트</b><p>admin 1280 input "text-clipped-x" 1건은 폼 input 값 스크롤(설계상 정상) 오탐 — form 컨트롤을 text-clip 판정에서 제외 → <b>게이트 7/7</b>(실 결함 검사 유지).</p></div></div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -793,6 +796,7 @@
|
|||
<tr><td>Turn evaluation SSOP</td><td><code>pytest app/test_session_turn_persistence.py app/test_evaluation_persistence.py app/test_voice_ws.py</code></td><td>20 tests OK; submit/stream/voice fast-loop evaluation normalized mapping + evaluator-only raw RLS + turn insert id 확인</td></tr>
|
||||
<tr><td>Postgres RLS/audit smoke checker</td><td><code>python scripts\check-postgres-rls-audit.py --help</code></td><td>py_compile/help/safe default OK; live DSN run still required for proof</td></tr>
|
||||
<tr><td>OAuth/BFF cookie security</td><td><code>app.test_auth_providers</code></td><td>20 tests OK; PKCE/state, cookie-bound signed state restart recovery, state cookie mismatch reject, opaque HttpOnly/Secure cookie, no browser tokens, SAML AuthnRequest/dev-only fixture ACS, dev/Tailnet OAuth guard, public-origin dev-login closed</td></tr>
|
||||
<tr><td>H4 consent gate</td><td><code>pytest app/test_auth_providers.py app/test_session_turn_persistence.py app/test_persona_review.py app/test_voice_ws.py -q</code></td><td>64 passed; learner consent accept/withdraw, non-learner/unaccepted reject, no-consent session start 403 <code>consent_required</code>, voice dev persona consent guard, frontend generated API contract updated</td></tr>
|
||||
<tr><td>Persona review workflow</td><td><code>app.test_persona_review</code></td><td>19 tests OK; approved-only catalog, teacher/admin queue, draft authoring, approve/reject audit, learner 403</td></tr>
|
||||
<tr><td>Voice WS contract</td><td><code>app.test_voice_ws</code></td><td>5 tests OK; auth guard, audio_start/chunks/audio_end, text_turn, ping, max audio cap</td></tr>
|
||||
<tr><td>Voice preset/EOT</td><td><code>app.test_voice_service</code></td><td>11 tests OK; TTS payload, preset fallback, P1 sample TTS, EOT readiness</td></tr>
|
||||
|
|
@ -819,7 +823,7 @@
|
|||
<tr><td>Public login</td><td><code>auth.spec.ts --grep public login</code></td><td>1 passed</td></tr>
|
||||
<tr><td>Public runtime scripts</td><td><code>start/watch/install-public-runtime*.ps1</code></td><td>parser OK; watchdog check-only healthy. Default public checks exclude not-yet-live <code>api-vnet.18ka.net</code>; add future domains explicitly with <code>-AdditionalPublicHealthUrls</code>.</td></tr>
|
||||
<tr><td>Public API health</td><td><code>https://api-vignette.chanpaca.net/health</code></td><td>prod, db true, engine true</td></tr>
|
||||
<tr><td>Public/local/Tailnet login recovery</td><td><code>https://vignette.chanpaca.net/login</code> / <code>http://localhost:5173/login</code> / <code>https://alpaca-home.taile93291.ts.net/login</code></td><td>public login 200, local/Tailnet login 200, public Google auth redirect 302 with HttpOnly state cookie, cookie-bound signed OAuth state survives API restart(fake code → <code>token_exchange_failed</code>, not <code>invalid_state</code>), provider callback error → <code>oauth=access_denied</code>, OAuth callback 실패는 <code>api.public.err.log</code>에 reason/status만 비밀값 없이 남김, 로그인 화면은 reason code 표시, local/Tailnet Google direct → <code>local_oauth_unavailable</code>, local auth E2E 7 passed, Tailnet auth/dev-login E2E 2 passed</td></tr>
|
||||
<tr><td>Public/local/Tailnet login recovery</td><td><code>https://vignette.chanpaca.net/login</code> / <code>http://localhost:5173/login</code> / <code>https://alpaca-home.taile93291.ts.net/login</code></td><td>2026-06-27 재확인: 모바일 502 시각의 cloudflared 로그는 <code>127.0.0.1:8001</code> origin refused였고 현재 public login 200, public API health prod/db/engine true. Tailnet 403은 stale Vite allowedHosts 문제였고 <code>start-tailscale-runtime.ps1</code> 재기동 후 login 200 + `/api/health` dev/db/engine true. public Google auth redirect 302 with HttpOnly state cookie, local/Tailnet Google direct → <code>local_oauth_unavailable</code>.</td></tr>
|
||||
<tr><td>Local 5175 login</td><td><code>PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 auth.spec.ts</code></td><td>desktop/mobile passed</td></tr>
|
||||
<tr><td>Learner/readiness E2E</td><td><code>learner.spec.ts + readiness.spec.ts desktop/mobile</code></td><td>14 passed</td></tr>
|
||||
<tr><td>Learner screenshots</td><td><code>learn-empty-desktop.png / learn-empty-mobile-compact.png</code></td><td>document overflow 0, empty history visible</td></tr>
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
| **H1** | 계약 평가 KPI(자기효능감·기술숙련도·수련만족도 사전사후) 수집·집계 전무 | `자기효능감/사전사후/수련만족/실험통제군` grep 0건. Phase3 KPI도 report shape만, 계산 코드 0줄. (분석) | 3척도 pre-post 폼·실험/통제군 배정·자동누적 대시보드·추이 시각화·검정 계산 코드. (κ/ICC·환각률은 doc4 미명시 → 평가설계 확정.) | doc4(20명 실험/통제군·단회기 50분·3척도 pre-post) |
|
||||
| **H2** | 턴별 회기 리뷰 fast-loop 1차 가동·골든셋/2열 UI 잔여 | `make_eval_hook`이 submit/voice 생성 경로에 주입되고, stream은 `_evaluate_stream_turn`으로 fast-loop 평가를 붙인다. 결과는 `feedback_scores`, `alternative_utterance` 등 정규화 테이블에 적재·hydrate된다. | 회기리뷰 UI 좌(축어록 타임라인+비언어)/우(기법·적절성·대안반응·이론) 2열 고도화, 원천 축어록 few-shot 골든셋 적재. | doc2·doc5(골드 포맷) |
|
||||
| **H3** | 임상팀 콘텐츠 입력 경로(페르소나 저작 CRUD) 부재 + P4~P7 미적재 | personas 라우트에 검수 승인/반려만, draft 생성·편집 API 없음. `persona_repository.py`는 in-code `SEED_PERSONAS`(P1~P3)만 materialize, 외부 JSON 미로드 ✓. | 페르소나 저작 CRUD(draft→review) + P4~P7 적재, 루브릭·이론 콘텐츠를 임상팀 편집 가능 데이터로 외부화. | doc3(R&R)·doc4(페르소나=전문가 산출물) |
|
||||
| **H4** | PII 마스킹 한국어 이름/기관 NER + 동의 게이트 잔여 | Presidio `language='en'` 고정이라 이름/기관명은 NER 보강이 필요하지만, 한국어 날짜·금액·행정구역 주소 정규식 폴백은 추가됐다. 외부 LLM 호출은 상담 생성(generate/stream)·fast/deep 평가 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency만 적재하도록 연결했고, prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log` 3행 증가를 확인했다. `consent_at` 컬럼만 있고 동의 수집/게이트/철회 엔드포인트는 아직 없다. | 한국어 이름/기관 NER 추가, 미성년/guardian 동의 수집·게이트·철회. 공개 Google OAuth 실제 `/turn` proof는 별도 운영 게이트. | doc1/2/5(실명·날짜·미성년·자살시도 다수)·doc4(IRB·개인정보) |
|
||||
| **H4** | PII 마스킹 한국어 이름/기관 NER + 동의 게이트 잔여 | Presidio `language='en'` 고정이라 이름/기관명은 NER 보강이 필요하지만, 한국어 날짜·금액·행정구역 주소 정규식 폴백은 추가됐다. 외부 LLM 호출은 상담 생성(generate/stream)·fast/deep 평가 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency만 적재하도록 연결했고, prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log` 3행 증가를 확인했다. `app_user.consent_at` 기반 학습자 동의 수락/철회 엔드포인트와 회기 시작·voice dev persona 시작 하드게이트를 추가했고, 프론트 세션 시작 전 동의 UI와 E2E 동의 seed를 연결했다. | 한국어 이름/기관 NER 추가, 미성년/guardian 및 법무 검토가 필요한 서명 동의서/개인정보 고지 evidence 확보. 공개 Google OAuth 실제 `/turn` proof는 별도 운영 게이트. | doc1/2/5(실명·날짜·미성년·자살시도 다수)·doc4(IRB·개인정보) |
|
||||
|
||||
### Medium+ (6) — M1~M3, X1~X2, L1
|
||||
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
- **C3 1차 완료**: `theory_mode`가 세션·평가·생성 프롬프트까지 흐른다. 후속은 임상팀 CBT 체인·이론부합 루브릭·명시적 선택 UI.
|
||||
- **H2 1차 완료**: `make_eval_hook`과 stream 평가가 턴 파이프라인에 붙고 정규화 테이블로 적재·복원된다. 대안발화도 `app.alternative_utterance`로 정규화한다. 후속은 2열 리뷰 UI·골든셋.
|
||||
- **H3**: 페르소나 저작 CRUD(draft→review) + P4~P7 로드 경로.
|
||||
- **H4(부분)·X1·X2**: 마스킹 한국어 정규식 보강과 외부 LLM 호출 metadata-only `audit.llm_call_log` 적재 경로, dry-run JSONL export·PII scan·IAA 계산 1차는 완료했다. `ds.*` write는 `--write-dataset` 명시 시에만 수행한다. X2 비용 관측·예산 경고와 evaluator fast/deep 모델 override 1차는 완료했고 semantic cache·장기 한도 정책은 후속. M1/M2도 1차 구현 완료, provider 기반 비언어 감지와 case_digest/pinned_fact 실적재는 후속.
|
||||
- **H4(부분)·X1·X2**: 마스킹 한국어 정규식 보강, 외부 LLM 호출 metadata-only `audit.llm_call_log` 적재 경로, 학습자 동의 수락/철회/회기 시작 하드게이트 골격은 완료했다. 한국어 이름/기관 NER와 guardian/legal 서명 evidence는 후속이다. dry-run JSONL export·PII scan·IAA 계산 1차도 완료했고 `ds.*` write는 `--write-dataset` 명시 시에만 수행한다. X2 비용 관측·예산 경고와 evaluator fast/deep 모델 override 1차는 완료했고 semantic cache·장기 한도 정책은 후속. M1/M2도 1차 구현 완료, provider 기반 비언어 감지와 case_digest/pinned_fact 실적재는 후속.
|
||||
|
||||
### B. 소유자 결정 / 외부(임상팀·기관) 의존
|
||||
|
||||
|
|
|
|||
|
|
@ -75,5 +75,7 @@
|
|||
- 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.**
|
||||
- 공개/로컬/Tailnet 로그인 복구: public API 530 원인은 prod에서 개발 전용 `VIGNETTE_VOICE_POC_SAMPLE_TTS=true`가 fail-close된 것과 DB `app.admin_engine_config` 기본 행 부재였다. `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를 분리한다. **검증: `https://vignette.chanpaca.net/login` 200 + `index-BzQmv-jU.js`, `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`, local/Tailnet Google direct는 각 origin의 `/login?oauth=local_oauth_unavailable`, local auth E2E 7 passed, Tailnet auth/dev-login E2E 2 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.**
|
||||
- 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를 분리한다. **검증: `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`, local/Tailnet Google direct는 각 origin의 `/login?oauth=local_oauth_unavailable`, `https://alpaca-home.taile93291.ts.net/login` 200 + `/api/health` dev/db/engine true, 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 분리 후 진행해야 한다.**
|
||||
|
|
|
|||
|
|
@ -76,8 +76,8 @@
|
|||
|
||||
### [high] H4. PII 마스킹 한국어 이름/기관 NER + 동의 게이트 잔여
|
||||
- **근거**: doc1/2/5(실명·날짜·장소·금액·미성년·자살시도 다수), doc4(IRB·개인정보)
|
||||
- **현재상태**(재검증): Presidio language='en' 고정으로 이름/기관명은 NER 보강이 필요하다. 한국어 날짜·금액·행정구역 주소 정규식 폴백은 추가됐고, 상담 생성(generate/stream)·fast/deep 평가 호출 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency만 적재한다. prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log` 3행 증가를 확인했다. consent_at 컬럼만, 동의 수집/게이트/철회 엔드포인트 전무.
|
||||
- **권고**: 한국어 이름/기관 NER 추가, 미성년/guardian 동의 수집·하드게이트·철회. 공개 Google OAuth 실제 `/turn` proof는 별도 운영 게이트.
|
||||
- **현재상태**(재검증): Presidio language='en' 고정으로 이름/기관명은 NER 보강이 필요하다. 한국어 날짜·금액·행정구역 주소 정규식 폴백은 추가됐고, 상담 생성(generate/stream)·fast/deep 평가 호출 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency만 적재한다. prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log` 3행 증가를 확인했다. `app_user.consent_at` 기반 학습자 동의 수락/철회 엔드포인트와 회기 시작·voice dev persona 시작 하드게이트를 추가했다. 프론트 세션 시작 전 동의 체크 UI와 E2E dev-login 동의 seed도 연결했다.
|
||||
- **권고**: 한국어 이름/기관 NER 추가, 미성년/guardian 및 법무 검토가 필요한 서명 동의서/개인정보 고지 evidence 확보. 공개 Google OAuth 실제 `/turn` proof는 별도 운영 게이트.
|
||||
|
||||
### [medium] M1. 비언어/준언어 임상 이벤트 캡처·태깅 부재
|
||||
- **근거**: doc1/2/5(침묵 초·한숨·울음·떨림을 1급 단서)
|
||||
|
|
@ -115,7 +115,7 @@
|
|||
- C3 1차 완료: `theory_mode`가 세션·평가·생성 프롬프트까지 흐른다. 후속은 임상팀 CBT 체인·이론부합 루브릭·명시적 선택 UI.
|
||||
- H2 1차 완료: `make_eval_hook`과 stream 평가가 턴 파이프라인에 붙고 정규화 테이블로 적재·복원된다. 대안발화도 `app.alternative_utterance`로 정규화한다. 후속은 2열 리뷰 UI·골든셋.
|
||||
- H3: 페르소나 저작 CRUD + P4~P7 로드 경로.
|
||||
- H4(부분): 한국어 이름/기관 NER, 동의 게이트. LLM call audit 적재와 로컬 live row proof는 완료.
|
||||
- H4(부분): 한국어 이름/기관 NER, guardian/legal 서명 동의 evidence. LLM call audit 적재, 로컬 live row proof, 학습자 동의 수락/철회/회기 시작 하드게이트 골격은 완료.
|
||||
- X1: dry-run JSONL export·PII scan·IAA 계산 1차 완료. `ds.*` write는 `--write-dataset` 명시 시에만 수행한다. X2 비용 관측·budget alert와 evaluator 모델 override 1차는 완료했고 semantic cache·장기 한도 정책은 후속. M1/M2도 1차 구현 완료, provider 기반 비언어 감지와 case_digest/pinned_fact 실적재는 후속.
|
||||
|
||||
### B. 소유자 결정 / 외부(임상팀·기관) 의존
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
- gateway `127.0.0.1:9099`
|
||||
- Tailnet API `127.0.0.1:8010`
|
||||
- web `127.0.0.1:5173`
|
||||
- 2026-06-27 17:00 KST 재확인: Tailnet `GET /login`이 `403 Blocked request. This host ... is not allowed`를 반환하던 원인은 Vite가 stale env로 떠서 `VITE_ALLOWED_HOSTS=alpaca-home.taile93291.ts.net`를 반영하지 못한 상태였다. `scripts/start-tailscale-runtime.ps1 -ApiPort 8010 -WebPort 5173 -SkipServeUpdate` 재기동 후 `GET /login` 200, `GET /api/health` dev/db/engine true, direct Google start 302 `local_oauth_unavailable`로 복구 확인.
|
||||
|
||||
## 공개 vnet.18ka.net 상태
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue