음성 재생과 운영 배포 정리

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

@ -322,7 +322,7 @@
<div class="ocol">
<div class="ocol-h">🧭 소유자 결정 (DECIDE) <span class="ct" id="ct-decide">0</span></div>
<div class="ostack" data-owner-col="decide">
<article class="ocard" data-owner="1"><div class="otop"><span class="otag decide">결정</span><span class="oblocks">아키텍처·일정</span></div><h3>백엔드 언어 (Python 유지 vs Node/Spring)</h3><p class="ask">PDF는 Node.js 우선(팀 보유기술·5개월 일정)/Spring 대안. 현재 Python/FastAPI — 재작성은 일정·인력 리스크.</p><p class="why">영향: 팀 역량·유지보수 vs 재작성 비용. 람다 단독 결정 금지.</p></article>
<article class="ocard" data-owner="0"><div class="otop"><span class="otag decide">결정</span><span class="oblocks">아키텍처·일정</span></div><h3>백엔드 언어 방향</h3><p class="ask">소유자 결정: 장기 교체 대상과 신규 분리 서비스는 Node.js 우선. 단, 현재 FastAPI 전면 재작성은 일정 리스크라 계약 우선 strangler 방식으로 간다.</p><p class="why">영향: 팀 역량은 Node로 맞추되, 납품 핵심 경로는 기존 Python 동작을 보존하며 교체 가능한 경계부터 만든다.</p></article>
<article class="ocard" data-owner="1"><div class="otop"><span class="otag decide">결정</span><span class="oblocks">Phase 3 측정</span></div><h3>평가설계 (H1: 실험/통제군·3척도·κ/ICC)</h3><p class="ask">doc4가 20명 실험/통제군·단회기 50분·3척도 pre-post를 명시. κ/ICC·환각률 목표는 doc4 미명시 — 확정 필요.</p><p class="why">영향: 평가 타당도, Phase 3 측정력. 평가설계는 소유자 결정.</p></article>
<article class="ocard" data-owner="1"><div class="otop"><span class="otag decide">결정</span><span class="oblocks">음성 아키텍처</span></div><h3>s2s 2차 PoC 채택 여부</h3><p class="ask">캐스케이드 1차는 확정. <code>voice-s2s-poc.md</code> 기준표로 비교 후 유지/폐기.</p><p class="opt">캐스케이드만 / s2s 2차 PoC 유지</p></article>
<article class="ocard" data-owner="1"><div class="otop"><span class="otag decide">결정</span><span class="oblocks">페르소나 음성</span></div><h3>서비스용 음성 모델·권리 정책</h3><p class="ask">Higgs급 무참조 synthetic P1 샘플은 생성 완료, dev-only sample TTS provider 연결 완료. 실존 인물/성우 ref 사용 금지 확정. 서비스 배포 전 상업 라이선스·동의 ref·대체 provider 중 결정 필요.</p><p class="opt">Higgs 라이선스 / 동의 ref / 대체 TTS</p></article>
@ -336,7 +336,7 @@
<div class="ostack" data-owner-col="ext">
<article class="ocard" data-owner="1"><div class="otop"><span class="otag ext">조율</span><span class="oblocks">위기게이트·평가·이론</span></div><h3>임상팀 콘텐츠 (구훈정·어유경)</h3><p class="ask">필요: C1 워크시트 항목·루브릭, CBT 이론별 단계 프롬프트 체인, 위기 스크립트·생명유지서약 문안, H2 평가 골든셋.</p><p class="why">왜 당신인가: doc3 R&R상 콘텐츠는 임상팀 산출물. 코드는 '편집 가능 구조'만 선제 구축 → 조율·수급 필요.</p></article>
<article class="ocard" data-owner="1"><div class="otop"><span class="otag ext">수급</span><span class="oblocks">평가 타당도·다양성</span></div><h3>추가 축어록 수급 규모/일정</h3><p class="ask">n=1 시드 한계 해소를 위한 추가 사례 수급 규모·일정 확정.</p><p class="why">영향: 평가 타당도, 페르소나 다양성, Phase 3 측정력.</p></article>
<article class="ocard" data-owner="1"><div class="otop"><span class="otag ext">거버넌스</span><span class="oblocks">계약·일정</span></div><h3>스택 정합성·저작권 등재 (L1)</h3><p class="ask">doc4 신청서 스택(Spring/Node·TimescaleDB) vs 실제 FastAPI/Python 불일치. 20주 단기일정·9월 저작권 등재 압박.</p><p class="why">권고: 스택 정합 또는 변경사유 거버넌스 기록.</p></article>
<article class="ocard" data-owner="1"><div class="otop"><span class="otag ext">거버넌스</span><span class="oblocks">계약·일정</span></div><h3>스택 정합성·저작권 등재 (L1)</h3><p class="ask">방향은 Node.js 교체 가능성 우선으로 정했다. 남은 일은 FastAPI 유지 사유, 계약 우선 전환 계획, 저작권 등재 문서 반영을 거버넌스 증거로 남기는 것이다.</p><p class="why">권고: <code>docs/decisions/backend-node-transition.md</code> 기준으로 신청서/등재 문서의 스택 설명을 정합화한다.</p></article>
</div>
</div>
</div>
@ -458,6 +458,8 @@
<p class="dg-note">9차 적용(2026-06-27): 관리자 콘솔 IA를 <code>/admin</code> 운영 홈과 <code>/admin/users</code> 사용자 관리, <code>/admin/access</code> 접근 권한, <code>/admin/tickets</code> 운영 티켓 메뉴로 분리했다. 운영 홈은 실제 API가 있는 교육 진행·온라인 사용자·헬스·AI 비용·서비스 리소스를 우선 표시하고, 티켓/업타임 이력은 <code>app.support_ticket</code>·<code>app.admin_health_event</code> 기반 1차 저장 모델로 연결했다. 세분 그룹 권한 enforcement는 아직 정책 화면이며 가짜 권한 효과를 표시하지 않는다.</p>
<p class="dg-note">17차 적용(2026-06-28): 운영 홈의 티켓 요약은 <code>resolved</code>/<code>closed</code> 이력을 진행 중 위험으로 세지 않는다. <code>high_priority_count</code>와 미니 티켓 목록은 미해결 티켓 기준이며, E2E 운영 티켓 테스트는 제목·본문에 테스트임을 명시하고 실패 시에도 cleanup patch로 해결 처리한다.</p>
<p class="dg-note">19차 적용(2026-06-28): <code>/admin/tickets</code>는 현재 범위를 미해결 큐 목록과 상태 변경으로 좁혔다. 정적 분류/인사이트 탭은 제거하고, 카드에 카테고리·신고자·경로·접수/갱신 시각을 표시한다. 해결된 항목은 최근 해결 이력으로 내려간다. 필터·카테고리 큐·Claude Recipe headless 자동 처리·이슈 등록은 백로그 <code>운영 티켓 자동 분류·처리 2차</code>로 남겼다.</p>
<p class="dg-note">26차 적용(2026-06-28): <code>/admin/tickets</code>에 검색, 상태·카테고리·우선순위·담당그룹·정체 필터와 카테고리 quick queue를 추가했다. 서버는 동일 query를 DB에서 처리하고, 티켓 접수 및 관리자 수동 처리 변경은 <code>audit.audit_log</code>에 metadata-only로 남긴다. 중복 병합, Claude Recipe 자동 수정 후보, 이슈/PR/작업 스레드 생성은 운영자 승인 전까지 계속 후속으로 둔다.</p>
<p class="dg-note">27차 적용(2026-06-28): 백엔드 언어 방향은 Node.js 우선 교체 가능성으로 정리하되 FastAPI 전면 재작성은 보류했다. 첫 경계로 <code>app/contracts/engine_gateway.py</code>를 추가해 <code>/v1/generate</code>, <code>/v1/stream</code>, SSE <code>token/done/error</code> 계약을 FastAPI client와 현재 Python gateway가 함께 쓰게 했다. 결정 기록은 <code>docs/decisions/backend-node-transition.md</code>에 둔다.</p>
<p class="dg-note">20차 적용(2026-06-28): 신규 Google/SAML 사용자는 <code>account_status=pending</code>으로 시작하고 승인 전에는 <code>/pending</code> 안내 화면만 본다. <code>yunchan@twentyoz.kr</code>는 슈퍼 관리자 allowlist로 admin+approved를 받으며, <code>/admin/users</code>는 가입 승인 탭에서 pending 계정을 승인 또는 보류 처리한다.</p>
<p class="dg-note">21차 적용(2026-06-28): 관리자 페이지 진입권을 기본 역할과 분리해 <code>app_user.admin_access</code>로 저장한다. <code>AUTH_SUPER_ADMIN_EMAILS</code> 기본값은 <code>yunchan@twentyoz.kr</code>, <code>hoonjungkoo@hs.ac.kr</code>이며, 슈퍼 관리자는 학습자·교수자·관리자 공간 전환과 관리자 권한 부여/회수를 할 수 있다. 학생·교수 계정도 <code>admin_access=true</code>면 우측 상단 관리자 진입이 노출된다. 구성 슈퍼 관리자의 권한 회수와 계정 비활성화는 차단한다.</p>
<p class="dg-note">25차 적용(2026-06-28): <code>/admin/users</code>에서 허용 도메인 밖 이메일도 정확한 계정 단위로 강제 등록할 수 있다. Google/SAML/dev-login은 미리 등록된 이메일만 도메인 게이트 예외로 통과시키고, provider 로그인 시 기존 관리 row의 역할·코호트·승인 상태를 이어받는다. 미등록 외부 도메인 로그인은 계속 차단한다.</p>
@ -591,8 +593,8 @@
<div class="scard-body"><div class="kv"><b>현재</b><p>교수자 성장 집계와 중복되던 점수·라포·기법 계산을 <code>app.services.session_metrics</code>로 공용화했다. <code>GET /sessions/dashboard</code>는 학습자 본인 세션만 집계해 <code>overview</code>, <code>growth</code>, <code>persona_progress</code>, <code>achievements</code>, <code>recent_feedback</code>를 반환하고, <code>LearnerHome</code>은 누적 회기·리뷰 대기·최근 평가·라포 흐름·페르소나별 진행·최근 피드백·마지막 세션 리캡을 실제 서버 데이터로 보여준다. 성취는 공식 수료/등급이 아니라 실제 연습 milestone만 사용한다.</p></div><div class="kv"><b>검증</b><p><code>pytest app/test_learner_dashboard.py app/test_teacher_dashboard.py app/test_rbac_idor.py -q</code> 12 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>, <code>learner.spec.ts</code> 6 passed, <code>session-review.spec.ts</code> 2 passed, <code>layout-visual-gate.spec.ts</code> 7 passed, <code>session-layout.spec.ts</code> 4 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>UX 감사 기준으로 2차에서는 카드 격자 비중을 줄이고 게이지·small multiples·테이블 중심으로 정리해야 한다. 모바일에서 임상 요약을 단순 숨김 처리하는 부분도 접힘/요약으로 바꾸는 게 맞다.</p></div></div>
</article>
<article class="scard" data-status="planned" data-cat="거버넌스·아키텍처" data-owner="1">
<button class="scard-head" aria-expanded="false"><span class="chip c-plan">중기</span><span class="scard-mid"><span class="scard-title">2-tier 모델 전략 + 백엔드 언어(원안 차이)<span class="ownbadge">내 차례</span></span><span class="scard-sum">2-tier는 ENGINE_MODE로 점진 도입 가능. 백엔드 언어는 소유자 결정.</span></span><span class="caret" aria-hidden="true"></span></button>
<div class="scard-body"><div class="kv"><b>현재</b><p>① PDF: 고성능(Claude·GPT=내담자발화·실시간평가)/저비용 한국어(Solar=단순응답·사후요약) 2단계 + 캐싱 90%절감. 현재 client AI는 claude_cli 기본 라우팅, fast/deep evaluator는 <code>EVALUATOR_FAST_MODEL</code>/<code>EVALUATOR_DEEP_MODEL</code> 설정 시 모델 override를 gateway에 전달한다. 벤더중립 인터페이스와 L0~L2 cache hint/session reuse는 있음. ② PDF는 <b>Node.js 우선</b>(팀 보유기술·5개월 일정)/Spring 대안. 현재 <b>Python/FastAPI</b> — 재작성은 일정·인력 리스크.</p></div><div class="kv k-warn"><b>권고</b><p>2-tier는 evaluator 모델 override 이후 client AI/요약 모델 정책과 semantic cache로 점진 도입. 백엔드 언어는 <b>소유자 결정</b>(팀 역량·유지보수 vs 재작성 비용).</p></div></div>
<button class="scard-head" aria-expanded="false"><span class="chip c-plan">중기</span><span class="scard-mid"><span class="scard-title">2-tier 모델 전략 + Node 교체 가능성<span class="ownbadge">내 차례</span></span><span class="scard-sum">2-tier는 ENGINE_MODE로 점진 도입 가능. 백엔드는 Node.js 우선 교체 가능성 방향으로 결정.</span></span><span class="caret" aria-hidden="true"></span></button>
<div class="scard-body"><div class="kv"><b>현재</b><p>① PDF: 고성능(Claude·GPT=내담자발화·실시간평가)/저비용 한국어(Solar=단순응답·사후요약) 2단계 + 캐싱 90%절감. 현재 client AI는 claude_cli 기본 라우팅, fast/deep evaluator는 <code>EVALUATOR_FAST_MODEL</code>/<code>EVALUATOR_DEEP_MODEL</code> 설정 시 모델 override를 gateway에 전달한다. 벤더중립 인터페이스와 L0~L2 cache hint/session reuse는 있음. ② 백엔드 방향은 <b>Node.js 우선 교체 가능성</b>으로 정했다. 단, 현재 <b>Python/FastAPI</b>는 납품 핵심 경로라 전면 재작성하지 않고, 계약 경계를 먼저 고정해 갈아치울 수 있게 만든다.</p></div><div class="kv k-warn"><b>권고</b><p>2-tier는 evaluator 모델 override 이후 client AI/요약 모델 정책과 semantic cache로 점진 도입. Node 전환은 <code>engine_gateway</code>, persona DTO/mapper, session read-model처럼 런타임 경계가 명확한 부분부터 strangler 방식으로 진행한다.</p></div></div>
</article>
<article class="scard" data-status="done" data-cat="코어·차별" data-owner="0">
<button class="scard-head" aria-expanded="false"><span class="chip c-done">검증 ✓</span><span class="scard-mid"><span class="scard-title">무회귀 검증 종합</span><span class="scard-sum">이번 전체 검증: Playwright E2E 113 + build/typecheck 통과.</span></span><span class="caret" aria-hidden="true"></span></button>
@ -638,13 +640,13 @@
<div class="scard-body"><div class="kv"><b>현재</b><p>M1: voice learner turn의 audio_ref, silence_ms, speech_rate, barge_in을 리뷰 API <code>nonverbal</code> 이벤트로 파생하고, 회기 리뷰 축어록에 침묵·발화 속도·끼어듦·음성 입력 칩으로 표시한다. 한숨·울음 감지와 live 마이크/STT/TTS 실측은 아직 별도다. M2: <code>case_profile</code>을 learner-persona 안정 case로 upsert하고, <code>session_no</code>를 트랜잭션 안에서 원자 증가시키며, 시작/턴/voice 경로가 직전 <code>session_summary</code> 기반 seed recall을 사용한다. episodic embedding writer와 case_digest/pinned_fact 실적재는 후속. M3: Google/SAML/dev-login이 <code>AUTH_EMAIL_COHORT_MAP</code>/<code>AUTH_DOMAIN_COHORT_MAP</code> 및 SAML cohort claim을 <code>cohort_ids</code>로 전달하고, DB <code>app_user.external_id</code>는 provider subject(<code>google:</code>/<code>saml:</code>/<code>dev:</code>) 기반으로 저장한다.</p></div><div class="kv k-warn"><b>권고</b><p>M1 한숨·울음/억양 등 provider 이벤트 보존, M2 session_summary digest/pinned_fact 압축·embedding writer·case_profile trajectory 갱신, M3 운영 SAML 서명검증·기관 claim schema/test tenant·deprovisioning audit는 외부 IdP 협의 후 진행.</p></div></div>
</article>
<article class="scard" data-status="planned" data-cat="거버넌스·아키텍처" data-owner="1">
<button class="scard-head" aria-expanded="false"><span class="chip c-plan">TRACK X1·X2 1차·L1</span><span class="scard-mid"><span class="scard-title">재귀학습 export(X1)·API 비용 관측(X2)·스택 정합성·일정(L1)<span class="ownbadge">내 차례</span></span><span class="scard-sum">X1/X2 1차와 evaluator 모델 override는 구현 완료. X1 approved export와 L1 거버넌스는 외부 gate가 남음.</span></span><span class="caret" aria-hidden="true"></span></button>
<div class="scard-body"><div class="kv"><b>현재</b><p>X1: <code>scripts/export-recursive-dataset.py</code><code>app.services.dataset_export</code>로 masked-text JSONL dry-run, PII scan, κ/ICC 계산, approved export 게이트를 구현했다. 기본은 <code>technical_dry_run</code>이고 <code>ds.*</code> write는 <code>--write-dataset</code> 명시 시에만 수행한다. X2: <code>app.turns</code>의 provider/model/tokens/cost를 최근 7일 기준으로 집계하는 <code>GET /admin/usage</code>를 추가했고, 운영 콘솔 <code>/admin</code>에 누적 비용·입출력 토큰·계량 커버리지·provider/model breakdown과 <code>ADMIN_USAGE_BUDGET_USD</code> 기반 예산 경고(ok/warn/exceeded)를 표시한다. <code>EVALUATOR_FAST_MODEL</code>/<code>EVALUATOR_DEEP_MODEL</code> 설정 시 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달하고, 비워두면 기존 default 라우팅을 유지한다. DB 미가용 dev에서는 runtime store로 fallback하지만 prod에서는 fail-closed한다. L1: doc4 신청서 스택(Spring Boot 3/Node.js·TimescaleDB) vs 실제 FastAPI/Python 불일치, 20주 단기일정·9월 저작권 등재 압박.</p></div><div class="kv k-warn"><b>권고</b><p>X1 approved export·골든셋 승격은 steward/legal 승인, reviewer disposition, IAA 게이트(κ≥0.6/ICC≥0.75) 이후만 진행한다. X2 후속은 semantic cache·장기 비용 추이/한도 정책·운영 모델별 비용 검증, L1은 스택 정합 또는 변경사유 거버넌스 기록.</p></div></div>
<button class="scard-head" aria-expanded="false"><span class="chip c-plan">TRACK X1·X2 1차·L1</span><span class="scard-mid"><span class="scard-title">재귀학습 export(X1)·API 비용 관측(X2)·스택 정합성·일정(L1)<span class="ownbadge">내 차례</span></span><span class="scard-sum">X1/X2 1차와 evaluator 모델 override는 구현 완료. L1은 Node 방향 결정과 gateway 계약 1차 완료, 등재 문서 반영은 외부 gate.</span></span><span class="caret" aria-hidden="true"></span></button>
<div class="scard-body"><div class="kv"><b>현재</b><p>X1: <code>scripts/export-recursive-dataset.py</code><code>app.services.dataset_export</code>로 masked-text JSONL dry-run, PII scan, κ/ICC 계산, approved export 게이트를 구현했다. 기본은 <code>technical_dry_run</code>이고 <code>ds.*</code> write는 <code>--write-dataset</code> 명시 시에만 수행한다. X2: <code>app.turns</code>의 provider/model/tokens/cost를 최근 7일 기준으로 집계하는 <code>GET /admin/usage</code>를 추가했고, 운영 콘솔 <code>/admin</code>에 누적 비용·입출력 토큰·계량 커버리지·provider/model breakdown과 <code>ADMIN_USAGE_BUDGET_USD</code> 기반 예산 경고(ok/warn/exceeded)를 표시한다. <code>EVALUATOR_FAST_MODEL</code>/<code>EVALUATOR_DEEP_MODEL</code> 설정 시 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달하고, 비워두면 기존 default 라우팅을 유지한다. DB 미가용 dev에서는 runtime store로 fallback하지만 prod에서는 fail-closed한다. L1: 소유자 결정으로 Node.js 우선 교체 가능성 방향을 채택했다. <code>docs/decisions/backend-node-transition.md</code>에 FastAPI 유지 사유와 strangler 전환 원칙을 기록했고, <code>app/contracts/engine_gateway.py</code>를 추가해 Python client와 현재 gateway가 같은 요청/응답/SSE 계약을 공유한다.</p></div><div class="kv k-warn"><b>권고</b><p>X1 approved export·골든셋 승격은 steward/legal 승인, reviewer disposition, IAA 게이트(κ≥0.6/ICC≥0.75) 이후만 진행한다. X2 후속은 semantic cache·장기 비용 추이/한도 정책·운영 모델별 비용 검증이다. L1의 남은 게이트는 신청서/저작권 등재 문서에 FastAPI 유지 사유와 Node 전환 계획을 반영하는 외부 거버넌스 증거다.</p></div></div>
</article>
</div>
<div class="detail-blocks">
<div class="detail-block"><h4>즉시 착수 가능(내부 코드, 외부 합의 불요)</h4><ul><li>C1은 저장형 워크시트까지 2차 완료. C2/C3/H2/H3은 1차 구조 완료. 후속은 임상 스크립트, CBT 체인, 2열 리뷰 UI, 페르소나 항목형 저작 UI처럼 데이터·정책·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>임상팀 산출물: C1 워크시트 항목·루브릭, C3 CBT 콘텐츠·체인, C2 위기 스크립트·서약 문안, H2 골든셋 코딩.</li><li>소유자 평가설계: H1 실험/통제군·3척도 문항·50분 흐름, κ/ICC·환각률 목표(doc4 미명시).</li><li>기관(한신 IT): M3 SSO IdP·test tenant·claim 스키마·인증서, 거버넌스 증거.</li><li>거버넌스: L1 Node 전환 방향은 결정됨. 남은 것은 FastAPI 유지 사유와 계약 우선 전환 계획의 저작권 등재 문서화·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>
</div>
@ -658,8 +660,8 @@
<div class="scard-body"><div class="kv"><b>현재</b><p>게이트웨이(9099 claude_cli)+API(8000, DB 미가용 시 in-memory degraded)+웹(5173)을 커맨드라인 기준 정리 후 결정론적 기동(<code>--reload</code> 워처 불안정 회피, uvicorn 있는 python 자동 해석). Docker가 사용 가능하면 <code>vignette-dev-db</code> healthcheck/pg_isready/role safety를 점검하고, compose <code>infra\.env</code> 필수값 누락도 경고한다. <code>-NoGateway</code>/<code>-NoWeb</code>는 해당 컴포넌트 정리도 건너뛰고, API 정리는 지정 <code>-ApiPort</code>만 대상으로 한다. dev-login + seed 페르소나. 로그 <code>.devlogs/</code>(gitignore). <b>검증</b>: 3포트 health OK + 실제 AI 턴 생성·회기리뷰 동작, API-only 재기동 후 8001/8010/5173/9099/20241 보존.</p></div><div class="kv k-warn"><b>비고</b><p>AI 턴은 로컬 claude CLI 사용(사용량 발생). 진입점 http://localhost:5173. <code>dev-down.ps1</code>은 기본적으로 DB 컨테이너를 보존하고, <code>-Db</code> 지정 시 함께 중지한다. 기존 <code>vignette-dev-db</code>는 accepting connections이나 healthcheck가 없고, <code>vignette_app</code>은 NOBYPASSRLS로 존재한다. 이번 패키징 smoke에서 fresh compose DB는 owner/app role 분리 상태로 API healthy까지 통과했고, 기존 dev DB의 운영 role 전환은 별도 owner migration 확인 후 진행한다. <b>주의(무DB 한정)</b>: DB 없이 degraded면 페르소나가 <code>source:seed_fallback·degraded:true</code>라 프론트 <code>isUsablePersona</code>가 막아 "회기 시작" 버튼이 비활성 → UI 세션 시작엔 실제 Postgres(<code>127.0.0.1:55432</code>)가 필요.</p></div></div>
</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">VOICE 작동</span><span class="scard-mid"><span class="scard-title">음성 채팅 "바로 꺼짐" 해결 — provider 키 부재였음</span><span class="scard-sum">키 없으면 1011 즉시 close. .env에 OPENAI_API_KEY 설정→정상. TTS 지문 제거·아바타 모션 수정.</span></span><span class="caret" aria-hidden="true"></span></button>
<div class="scard-body"><div class="kv"><b>현재</b><p>직접 WS 재현으로 근본원인 규명: 키 없으면 백엔드가 <code>degraded</code>+1011 즉시 close → 마이크 깜빡·녹음 미도달. STT(gpt-4o-transcribe)·TTS(gpt-4o-mini-tts) 모두 OpenAI. <b>실 OPENAI_API_KEY를 apps/api/.env(gitignore)에 설정 → /voice/health available:true</b>(degraded-close 사라짐). 백로그 B2 음성 캐스케이드가 로컬에서 실작동.</p></div><div class="kv"><b>부수 수정</b><p>① 미설정 시 마이크 버튼 사전 비활성+명확 안내 ② <b>TTS 지문 제거</b>: (고개 끄덕)·(한숨)·[침묵] 등 무대지시를 음성으로 안 읽음(<code>speakable_text</code>, 자막엔 유지) ③ <b>아바타 애니메이션</b>: 텍스트 턴 speaking 시 입 정지(0) 버그 수정 + thinking 표현 보강. typecheck+voice테스트 PASS.</p></div></div>
<button class="scard-head" aria-expanded="false"><span class="chip c-done">VOICE 작동</span><span class="scard-mid"><span class="scard-title">음성 채팅 무음 해결 — provider + 브라우저 재생 경계 보강</span><span class="scard-sum">OPENAI_API_KEY 설정 확인, Web Audio unlock 재생, live STT/TTS WS smoke 통과.</span></span><span class="caret" aria-hidden="true"></span></button>
<div class="scard-body"><div class="kv"><b>현재</b><p>직접 WS 재현으로 근본원인 규명: 키 없으면 백엔드가 <code>degraded</code>+1011 즉시 close → 마이크 깜빡·녹음 미도달. 현재 로컬 <code>/voice/health</code><code>available:true</code>이고, 실제 P2 회기에서 MP3 발화를 <code>/voice/ws</code>로 보내 <b>STT transcript 52자 → client reply 105자 → TTS 바이너리 66 chunks/266,496 bytes</b>를 수신했다.</p></div><div class="kv"><b>부수 수정</b><p>① 미설정 시 마이크 버튼 사전 비활성+명확 안내 ② <b>TTS 지문 제거</b>: (고개 끄덕)·(한숨)·[침묵] 등 무대지시를 음성으로 안 읽음(<code>speakable_text</code>, 자막엔 유지) ③ <b>아바타 애니메이션</b>: 텍스트 턴 speaking 시 입 정지(0) 버그 수정 + thinking 표현 보강<b>브라우저 자동재생 차단 대응</b>: 마이크 클릭 시 <code>AudioContext.resume()</code>으로 unlock하고 TTS Blob을 Web Audio buffer source로 재생, 실패 시 기존 <code>&lt;audio&gt;</code> fallback. typecheck+voice테스트+voice E2E+live WS smoke PASS.</p></div></div>
</article>
<article class="scard" data-status="done" data-cat="코어·차별" data-owner="0">
<button class="scard-head" aria-expanded="false"><span class="chip c-done">페르소나 격리</span><span class="scard-mid"><span class="scard-title">AI 내담자 캐릭터 붕괴·개발맥락 누출 버그 수정</span><span class="scard-sum">--append→--system-prompt 교체 + dynamic 섹션 제외. 누출 0. 게이트웨이 7/7.</span></span><span class="caret" aria-hidden="true"></span></button>
@ -760,12 +762,12 @@
<div class="tab-panel" id="panel-p1" role="tabpanel" aria-labelledby="tab-p1" hidden>
<div class="detail-head"><div><h3>Phase 1 MVP 코어 루프 상세</h3><p>목표는 "P1 내담자와 실제 상담 1회 완주 후 피드백을 받는 공개 시연 가능 상태"다.</p></div><div class="mini-metrics"><span>우선순위: P0</span><span>범위: 텍스트+음성 1턴</span><span>완료보고: E2E 증거 후</span></div></div>
<div class="task-list">
<div class="task-row"><div><span class="task-status s-doing">CHECK</span></div><div><b>LLMEngine 정규화 계약 고정</b><p>gateway SSE <code>token/done/error</code>와 generate cost 계약은 회귀화했다. 이번 패스에서 <code>/v1/stream</code> route 자체가 <code>event: token</code>, <code>event: done</code>, <code>event: error</code> 프레임을 내는지 fake session으로 직접 고정했다. claude_cli와 Messages API 폴백 동일성 검증은 라이브 키/환경 의존이라 남아 있다.</p></div><div><b>산출물</b><p><code>engine_gateway/test_gateway_model.py</code> stream frame contract tests</p></div><div><b>검증</b><p><code>pytest engine_gateway/test_gateway_model.py -q</code> 9 passed; focused backend 묶음 40 passed.</p></div></div>
<div class="task-row"><div><span class="task-status s-doing">CHECK</span></div><div><b>LLMEngine 정규화 계약 고정</b><p>gateway SSE <code>token/done/error</code>와 generate cost 계약은 회귀화했다. 이번 패스에서 <code>app/contracts/engine_gateway.py</code>를 추가해 FastAPI <code>engine_client</code>와 현재 Python gateway가 같은 <code>GenerateRequest</code>/<code>GenerateResponse</code>/<code>Stream*Event</code> 계약을 import한다. Node.js gateway로 갈아치울 때 보존해야 할 첫 wire contract다. claude_cli와 Messages API 폴백 동일성 검증은 라이브 키/환경 의존이라 남아 있다.</p></div><div><b>산출물</b><p><code>app/contracts/engine_gateway.py</code>, <code>engine_gateway/test_gateway_model.py</code> shared contract + stream frame tests</p></div><div><b>검증</b><p><code>python -B -m py_compile app/contracts/engine_gateway.py app/engine_client.py engine_gateway/gateway.py</code>, <code>python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py -q</code> 11 passed.</p></div></div>
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>상주 <code>claude -p</code> 엔진풀 실증</b><p><code>session_id</code>가 살아 있는 gateway session을 재사용하고 missing session은 ephemeral로 닫히는 구조를 회귀화했고, live 게이트웨이(9099)로 실측까지 마쳤다. <code>probe-engine-gateway.py</code>가 단일 session_id로 2회 reused stream을 돌려 TTFT 16674199ms, cost 누적 0.068→0.123(turns 1→2), engine=claude_p, model=opus-4-8을 측정했다.</p></div><div><b>산출물</b><p><code>engine_gateway/test_gateway_model.py</code>, <code>scripts/probe-engine-gateway.py</code>, live probe JSON</p></div><div><b>검증</b><p>session reuse 7 tests OK; live probe 2 reused streams, TTFT/cost 실측, session_id 재사용 확인</p></div></div>
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>마스킹 게이트 + LLM call audit</b><p>current turn, recall, pinned fact, recent turns가 Presidio/가명처리 후에만 engine request로 들어가게 막았다. 외부 LLM 호출은 <code>audit.llm_call_log</code>에 provider/model/token/cost/inference_geo/latency metadata만 남기고 prompt/completion 본문은 저장하지 않는다.</p></div><div><b>산출물</b><p><code>app/test_orchestrator_masking.py</code>, <code>app/test_evaluation_persistence.py</code>, <code>session_persistence.record_llm_call_audit</code></p></div><div><b>검증</b><p>phone/email/RRN raw 값이 generate/stream payload에 없음, 감사 payload 본문 미포함, 관련 회귀 27 passed, 전체 API 최신 130 passed, 로컬 live <code>/turn</code> smoke 후 <code>audit.llm_call_log</code> 8→11</p></div></div>
<div class="task-row"><div><span class="task-status s-doing">CHECK</span></div><div><b>P1 상담 완주 플로우</b><p>통제된 UI E2E로 P1 선택, SSE 상담, 종료, 회기말 리뷰 피드백을 한 번에 통과시켰다. 실제 운영 로그인 proof는 P0 blocker와 함께 남아 있다.</p></div><div><b>산출물</b><p><code>e2e/session-mvp.spec.ts</code>, turn telemetry 저장 경로</p></div><div><b>검증</b><p><code>chromium-single-run</code> P1 MVP UI E2E 1 passed</p></div></div>
<div class="task-row"><div><span class="task-status s-doing">CHECK</span></div><div><b>저항 엔진 정량 검증</b><p>공감 발화와 조언점프 발화 5턴 세트를 순수 상태머신 회귀 테스트로 비교했다. DB 쿼리 기반 운영 증거는 남아 있다.</p></div><div><b>산출물</b><p><code>app/test_state_machine_resistance.py</code></p></div><div><b>검증</b><p>공감 시 탐색 전이/개방도 상승, 조언점프 시 라포 단계 유지/개방도 0</p></div></div>
<div class="task-row"><div><span class="task-status s-doing">CHECK</span></div><div><b>음성 왕복 1턴 PoC</b><p>Session UI 마이크 버튼 경로에서 synthetic <code>getUserMedia</code>/<code>MediaRecorder</code>로 실제 <code>/voice/ws</code>를 열고 audio_start, binary chunk, audio_end, transcript, reply, TTS 재생 상태까지 검증했다. 물리 마이크, 공개 WSS, live STT/TTS provider 증거는 아직 별도다.</p></div><div><b>산출물</b><p><code>apps/web/e2e/voice-success.spec.ts</code>, controlled OpenAI/engine providers, browser voice UI probe</p></div><div><b>검증</b><p><code>npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run</code> 2 passed</p></div></div>
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>저항 엔진 정량 검증</b><p>공감 발화와 조언점프 발화 5턴 세트를 상태머신 회귀 테스트와 실제 API+DB smoke로 비교했다. live smoke는 dev-login→onboarding→P1 세션 2개→각 5턴을 수행하고 <code>app.session_state</code>, <code>app.turns</code>, <code>app.turn_client_state</code>를 직접 조회했다. 공감 세션은 3턴부터 <code>탐색</code>으로 열리고 openness가 <code>0.171</code>까지 상승했다. 조언점프 세션은 <code>라포</code>와 openness <code>0.0</code>을 유지했다.</p></div><div><b>산출물</b><p><code>app/test_state_machine_resistance.py</code>, <code>scripts/smoke-resistance-openness-db.py</code>, <code>docs/ops/resistance-openness-db-smoke-2026-06-28.json</code></p></div><div><b>검증</b><p><code>pytest app/test_state_machine_resistance.py app/test_evaluation_persistence.py -q</code> 9 passed; live DB smoke PASS.</p></div></div>
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>음성 왕복 1턴 PoC</b><p>Session UI 마이크 버튼 경로에서 synthetic <code>getUserMedia</code>/<code>MediaRecorder</code>로 실제 <code>/voice/ws</code>를 열고 audio_start, binary chunk, audio_end, transcript, reply, TTS 재생 상태까지 검증했다. 추가로 로컬 live API에서 실제 STT/TTS provider를 통과해 TTS 바이너리를 수신했다. 물리 마이크, 공개 WSS, 50분 양방향 실측은 별도 운영 smoke로 남아 있다.</p></div><div><b>산출물</b><p><code>apps/web/e2e/voice-success.spec.ts</code>, controlled OpenAI/engine providers, Web Audio browser voice UI probe, live <code>/voice/ws</code> smoke</p></div><div><b>검증</b><p><code>npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run</code> 2 passed; live P2 WS smoke binary 66 chunks/266,496 bytes</p></div></div>
</div>
</div>
@ -852,10 +854,10 @@
<tr><td>Voice metadata</td><td><code>app.test_session_turn_persistence</code></td><td>audio_ref/silence_ms/speech_rate/barge_in persisted on learner voice turn; review <code>nonverbal</code> events exposed</td></tr>
<tr><td>P1 MVP UI E2E</td><td><code>npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run</code></td><td>1 passed</td></tr>
<tr><td>Persona/session review UI E2E</td><td><code>npx playwright test e2e/teacher.spec.ts --project=chromium-single-run</code></td><td>2 passed; teacher approves pending persona and opens pending ended-session review detail from console</td></tr>
<tr><td>Voice UI synthetic E2E</td><td><code>npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run</code></td><td>2 passed; direct WS cascade plus Session mic button path with synthetic browser audio</td></tr>
<tr><td>Voice UI synthetic E2E</td><td><code>npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run</code></td><td>2 passed; direct WS cascade plus Session mic button path with synthetic browser audio and Web Audio playback under blocked media-element autoplay</td></tr>
<tr><td>Voice s2s decision memo</td><td><code>docs/decisions/voice-s2s-poc.md</code></td><td>criteria recorded; keep/drop decision remains owner DECIDE</td></tr>
<tr><td>Hanshin data/SSO gate</td><td><code>docs/ops/hanshin-data-governance-gate.md</code></td><td>artifact created; written external evidence still required</td></tr>
<tr><td>Resistance engine</td><td><code>app.test_state_machine_resistance</code></td><td>2 tests OK; empathy vs advice-jump curve fixed</td></tr>
<tr><td>Resistance engine</td><td><code>app.test_state_machine_resistance</code> + <code>scripts/smoke-resistance-openness-db.py</code></td><td>9 focused tests OK; live DB smoke PASS. Empathy P1 curve <code>0→0→0.095→0.133→0.171</code> and DB final <code>stage=탐색</code>. Advice-jump curve stayed <code>0</code> and DB final <code>stage=라포</code>.</td></tr>
<tr><td>Python compile</td><td><code>python -m compileall app engine_gateway</code></td><td>Passed</td></tr>
<tr><td>Phase 3 artifact gates</td><td><code>python scripts\check-phase3-artifacts.py --help</code> + <code>pytest app/test_phase3_artifact_checker.py -q</code></td><td>Passed; checker now enforces CSV enums, KPI metric required fields, approved export PII/agreement/consent/withdrawal/file-hash gates. Actual pilot evidence still external.</td></tr>
<tr><td>Web build</td><td><code>npm run build</code></td><td>Passed</td></tr>
@ -913,7 +915,7 @@ $env:E2E_PUBLIC_STORAGE_STATE=".\node_modules\.tmp\public-auth.json"
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1</pre>
</div>
<div class="source-note"><b>닫은 항목(live 증거):</b> ① engine config 운영값(<code>claude_cli / 9099 / gateway-default</code>, durable, db). ② 상주 엔진풀 probe — 단일 session_id 2회 reused stream, TTFT 16674199ms, cost 누적 0.068→0.123. ③ 상주 엔진풀 RSS — gateway session 1개에서 <code>claude -p</code> child RSS 363.3MB, gateway RSS 16.2MB. ④ Postgres RLS/audit smoke 5 checks PASS. ⑤ turn cost telemetry — <code>app.turns</code> client_ai 13행에 provider/model/cost_usd 실적재(합 $1.22). ⑥ 레이아웃 시각 게이트 7/7 + 적대적 재검수 7/7 accept.</div>
<div class="source-note"><b>환경 제약으로 이 세션에서 못 닫는 항목(정직 표기):</b> 공개 Google OAuth 실제 <code>/turn</code>(실제 계정 storageState 필요) · <code>infra/.env</code> compose 필수값(<code>APP_DB_PASSWORD</code>, <code>OAUTH_GOOGLE_CLIENT_ID</code>, <code>OAUTH_GOOGLE_CLIENT_SECRET</code>) · <code>vnet.18ka.net</code>/<code>api-vnet.18ka.net</code> 공개 런타임(DNS 미해결, Cloudflare 18ka.net zone 권한 불일치, Google OAuth redirect URI 등록 필요) · 한신대 데이터/SSO 거버넌스(owner/기관 written evidence 필요) · 음성 live STT/TTS provider·물리 마이크·공개 WSS 50분 · 저항엔진 openness 곡선 DB 실증 · claude_cli↔Messages API 폴백 동일성 · 재부팅 후 watchdog smoke · Phase 3 파일럿 게이트 · decisions 6건(owner 결정). 운영 원칙상 가짜 증거로 DONE 표기하지 않는다. 전체 추적: <code>docs/ops/backlog-2026-06-26.md</code>(B1 폴리시 · B2 환경제약 · B3 소유자결정 · B4 거버넌스).</div>
<div class="source-note"><b>환경 제약으로 이 세션에서 못 닫는 항목(정직 표기):</b> 공개 Google OAuth 실제 <code>/turn</code>(실제 계정 storageState 필요) · <code>infra/.env</code> compose 필수값(<code>APP_DB_PASSWORD</code>, <code>OAUTH_GOOGLE_CLIENT_ID</code>, <code>OAUTH_GOOGLE_CLIENT_SECRET</code>) · <code>vnet.18ka.net</code>/<code>api-vnet.18ka.net</code> 공개 런타임(DNS 미해결, Cloudflare 18ka.net zone 권한 불일치, Google OAuth redirect URI 등록 필요) · 한신대 데이터/SSO 거버넌스(owner/기관 written evidence 필요) · 음성 live STT/TTS provider·물리 마이크·공개 WSS 50분 · claude_cli↔Messages API 폴백 동일성 · 재부팅 후 watchdog smoke · Phase 3 파일럿 게이트 · decisions 6건(owner 결정). 운영 원칙상 가짜 증거로 DONE 표기하지 않는다. 전체 추적: <code>docs/ops/backlog-2026-06-26.md</code>(B1 폴리시 · B2 환경제약 · B3 소유자결정 · B4 거버넌스).</div>
</div>
<div class="tab-panel" id="panel-decisions" role="tabpanel" aria-labelledby="tab-decisions" hidden>
<div class="detail-head"><div><h3>윤찬 결정 필요 상세</h3><p>람다가 단독으로 정하면 월권인 항목이다. 결정되기 전에는 "완료"가 아니라 조건부로 둔다.</p></div><div class="mini-metrics"><span>결정 6개</span><span>조건부 GO 사유</span></div></div>