docs: consolidate dashboard details into tabs
This commit is contained in:
parent
22ee0c6b89
commit
47c5b97a6d
1 changed files with 480 additions and 222 deletions
|
|
@ -135,6 +135,55 @@
|
||||||
color:var(--ink-2);
|
color:var(--ink-2);
|
||||||
font-size:13px;
|
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; }
|
code { font-family:var(--num); font-size:12px; background:var(--paper-2); padding:2px 6px; border-radius:6px; }
|
||||||
.wide { grid-column:1 / -1; }
|
.wide { grid-column:1 / -1; }
|
||||||
.commands { display:grid; gap:10px; }
|
.commands { display:grid; gap:10px; }
|
||||||
|
|
@ -149,6 +198,12 @@
|
||||||
.hero { grid-template-columns:1fr; }
|
.hero { grid-template-columns:1fr; }
|
||||||
.grid { grid-template-columns:1fr; }
|
.grid { grid-template-columns:1fr; }
|
||||||
.roadmap { 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) {
|
@media (max-width:520px) {
|
||||||
.summary { grid-template-columns:1fr; }
|
.summary { grid-template-columns:1fr; }
|
||||||
|
|
@ -160,7 +215,7 @@
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="brand"><span class="mark" aria-hidden="true"></span><span>Vignette 개발 대시보드</span></div>
|
<div class="brand"><span class="mark" aria-hidden="true"></span><span>Vignette 개발 대시보드</span></div>
|
||||||
<div class="stamp">2026-06-26 15:35 KST</div>
|
<div class="stamp">2026-06-26 16:04 KST</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|
@ -256,234 +311,437 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section aria-labelledby="detail-tabs-title">
|
||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<div>
|
<div>
|
||||||
<h2>팀장 판정</h2>
|
<h2 id="detail-tabs-title">상세 페이지</h2>
|
||||||
<p>이 HTML만 보고 판단해야 하는 항목</p>
|
<p>탭별 할 일, 산출물, 검증 기준. 팀장 리뷰는 여기서 다음 작업을 고르면 된다.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid">
|
<div class="tabs">
|
||||||
<article class="panel">
|
<div class="tabbar" role="tablist" aria-label="개발 상세 탭">
|
||||||
<span class="tag done">go</span>
|
<button class="tab-button" id="tab-p0" role="tab" aria-selected="true" aria-controls="panel-p0" data-tab="p0">P0 마감</button>
|
||||||
<h3>로컬 회귀와 공개 prod-safe 게이트 통과</h3>
|
<button class="tab-button" id="tab-p1" role="tab" aria-selected="false" aria-controls="panel-p1" data-tab="p1">P1 MVP</button>
|
||||||
<p>백엔드 unittest 20개, Python compile, 웹 production build, 전체 Playwright 72개가 통과했다. 공개 API도 `environment:"prod"`, `db:true`, `engine:true`로 응답한다.</p>
|
<button class="tab-button" id="tab-p2a" role="tab" aria-selected="false" aria-controls="panel-p2a" data-tab="p2a">P2a 권한/페르소나</button>
|
||||||
</article>
|
<button class="tab-button" id="tab-p2b" role="tab" aria-selected="false" aria-controls="panel-p2b" data-tab="p2b">P2b 음성</button>
|
||||||
<article class="panel">
|
<button class="tab-button" id="tab-p3" role="tab" aria-selected="false" aria-controls="panel-p3" data-tab="p3">P3 파일럿</button>
|
||||||
<span class="tag done">deployed</span>
|
<button class="tab-button" id="tab-ops" role="tab" aria-selected="false" aria-controls="panel-ops" data-tab="ops">운영/검증</button>
|
||||||
<h3>커스텀 도메인 운영 반영 완료</h3>
|
<button class="tab-button" id="tab-decisions" role="tab" aria-selected="false" aria-controls="panel-decisions" data-tab="decisions">결정 필요</button>
|
||||||
<p>Cloudflare Pages production `0fe8f3cc`이 `543d48c`로 배포됐고, `https://vignette.chanpaca.net`이 `index-D1Z5DH5J.js`와 `index-BA9-xxfn.css`를 서빙한다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag doing">conditional</span>
|
|
||||||
<h3>운영 반영 전 남은 증거 1개</h3>
|
|
||||||
<p>실제 Google 로그인 쿠키가 필요한 공개 `/turn` smoke는 아직 storageState가 없어 미완이다. 로그인 가능한 계정으로 한 번 캡처하면 바로 검증 가능하다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag info">mock audit</span>
|
|
||||||
<h3>런타임 목업 제거 기준</h3>
|
|
||||||
<p>프론트 페이지는 실제 API/DB 경로를 탄다. 남은 `mock` 표기는 테스트 코드의 `unittest.mock` 또는 의도적 fixture이며, seed fallback은 prod/staging에서 fail-closed 된다.</p>
|
|
||||||
</article>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
<div class="tab-panel" id="panel-p0" role="tabpanel" aria-labelledby="tab-p0">
|
||||||
<div class="section-head">
|
<div class="detail-head">
|
||||||
<div>
|
<div>
|
||||||
<h2>완료</h2>
|
<h3>Phase 0 마감 상세</h3>
|
||||||
<p>새 세션에서 반복하지 않아도 되는 작업</p>
|
<p>지금 단계는 구현을 더 벌리는 단계가 아니라, 운영 증거를 닫고 P1 진입 조건을 고정하는 단계다.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mini-metrics"><span>상태: 조건부</span><span>우선순위: P0</span><span>블로커: public /turn</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-list">
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-risk">BLOCK</span></div>
|
||||||
|
<div><b>공개 Google OAuth 실제 `/turn` proof 확보</b><p>로그인 가능한 계정으로 storageState를 저장한 뒤 public-auth-turn E2E를 실행한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>`apps/web/node_modules/.tmp/public-auth.json`, public `/turn` 성공 로그</p></div>
|
||||||
|
<div><b>검증</b><p>`E2E_PUBLIC_AUTH=1` + `chromium-public-auth` 1회 통과</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-doing">CHECK</span></div>
|
||||||
|
<div><b>engine config 운영값 재확인</b><p>E2E가 admin engine config를 만질 수 있으므로 배포 전 `mode/url/model` 값을 확인한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>admin AI 운영 화면 캡처 또는 config API 응답</p></div>
|
||||||
|
<div><b>검증</b><p>`claude_cli / http://127.0.0.1:9099 / gateway-default` 의도값 대조</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-done">DONE</span></div>
|
||||||
|
<div><b>과거 Live2D demo 자산 제거</b><p>Mao/Haru 샘플, Pixi/Cubism 런타임, 공개 `/live2d/*` 캐시 잔여 접근을 차단했다.</p></div>
|
||||||
|
<div><b>산출물</b><p>`apps/web/functions/live2d/[[path]].js`, SVG-only avatar</p></div>
|
||||||
|
<div><b>검증</b><p>운영 `/live2d/mao/*`, Cubism core `404`</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-done">DONE</span></div>
|
||||||
|
<div><b>프로세스 난립 정리</b><p>임시 Vite preview와 dev API를 내리고 운영 확인용 프로세스만 유지한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>9099 engine, 8001 prod API, cloudflared tunnel 1개</p></div>
|
||||||
|
<div><b>검증</b><p>`Get-NetTCPConnection`에서 8000/517x/5537 비어 있음</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">GATE</span></div>
|
||||||
|
<div><b>한신대 공문/데이터 거버넌스 게이트 정리</b><p>SSO 클레임, 추가 축어록 수급, 미성년 원본 활용동의, 개인정보 처리방침을 P1 진입 전 외부 의존성으로 명확히 둔다.</p></div>
|
||||||
|
<div><b>산출물</b><p>공문 질의서, 동의 범위 체크리스트, SSO claim 표</p></div>
|
||||||
|
<div><b>검증</b><p>IRB 게이트가 아니라 데이터/SSO 게이트로 문서 전수 정합</p></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid">
|
<div class="detail-blocks">
|
||||||
<article class="panel">
|
<div class="detail-block">
|
||||||
<span class="tag done">E2E persona decoupled</span>
|
<h4>팀장 판정</h4>
|
||||||
<h3>DB persona 기반 테스트</h3>
|
<ul>
|
||||||
<p>`session-persistence`, `teacher`, `voice`는 `/api/personas`에서 실제 DB persona를 가져온다.</p>
|
<li>로컬 회귀와 공개 prod-safe 게이트는 통과했다.</li>
|
||||||
</article>
|
<li>Cloudflare Pages production `0fe8f3cc`이 `543d48c` 소스로 배포됐다.</li>
|
||||||
<article class="panel">
|
<li>운영 반영 전 남은 핵심 증거는 공개 Google OAuth 실제 `/turn` smoke다.</li>
|
||||||
<span class="tag done">fail-closed</span>
|
<li>런타임 mock/demo 자산은 운영 번들에서 제거하고, 테스트 fixture만 남긴다.</li>
|
||||||
<h3>비개발 런타임 보호</h3>
|
|
||||||
<p>prod/staging에서 dev login, seed fallback, OAuth 누락, 기본 secret, localhost CORS를 거부한다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">infra</span>
|
|
||||||
<h3>compose OAuth 주입</h3>
|
|
||||||
<p>`ENVIRONMENT`, Google OAuth vars, `CORS_ORIGINS`, `OPENAI_BASE_URL`을 API 컨테이너에 명시했다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">admin UX</span>
|
|
||||||
<h3>모바일 overflow 수정</h3>
|
|
||||||
<p>긴 readiness detail을 안정화했고, 사용자 변경은 DB 저장소가 durable할 때만 허용한다. AI 운영은 실제 어댑터명과 health 상태를 표시한다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">test isolation</span>
|
|
||||||
<h3>global engine config 분리</h3>
|
|
||||||
<p>`db-persistence`는 전역 engine config를 바꾸므로 `@single-run`으로 분리했다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">docs</span>
|
|
||||||
<h3>handoff 갱신</h3>
|
|
||||||
<p>`docs/HANDOFF.md`, `docs/dev_dashboard.html`, `apps/web/e2e/README.md`를 최신 상태로 맞췄다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">public prod</span>
|
|
||||||
<h3>공개 API prod 전환</h3>
|
|
||||||
<p>`cloudflared` ingress를 `127.0.0.1:8001` prod API로 돌렸고 public readiness gate가 통과했다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">ops script</span>
|
|
||||||
<h3>공개 런타임 재기동</h3>
|
|
||||||
<p>`scripts/start-public-runtime.ps1`와 `scripts/stop-public-runtime.ps1`로 API 8001과 cloudflared를 재현 가능하게 만들었다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">auth boundary</span>
|
|
||||||
<h3>persona catalog 인증화</h3>
|
|
||||||
<p>`/personas`는 이제 비로그인 401이다. 로컬과 공개 API 모두 직접 확인했고 E2E 회귀를 추가했다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">public web</span>
|
|
||||||
<h3>API base fallback 보강</h3>
|
|
||||||
<p>public 웹에서 `/api/auth/config`가 SPA HTML로 떨어지지 않도록 public API origin fallback을 추가했다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">learner UX</span>
|
|
||||||
<h3>0회 기록 상태 보강</h3>
|
|
||||||
<p>학습자 홈은 이제 fake 기록 없이 실제 서버 세션 0개를 `기존 회기` 빈 상태와 0 카운트로 표시한다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">local login</span>
|
|
||||||
<h3>5175 로컬 재기동 검증</h3>
|
|
||||||
<p>`127.0.0.1:5175` web, `8000` API, `9099` engine을 재기동하고 dev-login 서버 세션과 `/learn` redirect를 확인했다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">session UX</span>
|
|
||||||
<h3>dense viewport 보정</h3>
|
|
||||||
<p>375px급 active session에서 stage 내부가 1px 넘게 잘리던 문제를 avatar/padding 조정으로 제거했다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">real stream</span>
|
|
||||||
<h3>텍스트 턴 SSE 전환</h3>
|
|
||||||
<p>`POST /sessions/{id}/stream` token 이벤트로만 내담자 자막을 쌓고, HTTP 거절과 SSE error 모두 실패 턴을 transcript와 저장소에 남기지 않는다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">voice turn</span>
|
|
||||||
<h3>음성 실패 턴 미저장</h3>
|
|
||||||
<p>음성 회기도 엔진 응답 생성 성공 후에만 learner/client turn을 저장한다. 실패한 transcript는 pending 상태에서 제거한다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">ops copy</span>
|
|
||||||
<h3>비영구 상태 문구 정리</h3>
|
|
||||||
<p>admin health와 voice degraded reason은 내부 fallback/seed 표현 대신 비영구 런타임 상태를 사용자용 문구로 표시한다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">route guard</span>
|
|
||||||
<h3>직접 URL persona 차단</h3>
|
|
||||||
<p>`/learn/session/:code`로 직접 들어와도 DB 원본 persona가 아니면 회기 시작을 막는다. degraded/seed fallback 회귀를 추가했다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag done">avatar</span>
|
|
||||||
<h3>demo Live2D 자산 제거</h3>
|
|
||||||
<p>Mao/Haru 샘플과 Pixi/Cubism 런타임을 공개 배포물에서 제거했다. 세션 화면은 SVG persona avatar만 렌더링한다.</p>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="section-head">
|
|
||||||
<div>
|
|
||||||
<h2>검증</h2>
|
|
||||||
<p>마지막으로 직접 실행한 결과</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<table>
|
|
||||||
<thead><tr><th>범위</th><th>명령</th><th>결과</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr>
|
|
||||||
<tr><td>Backend</td><td><code>python -m unittest app.test_runtime_policy app.test_session_turn_persistence engine_gateway.test_gateway_model</code></td><td>20 tests OK</td></tr>
|
|
||||||
<tr><td>Compile</td><td><code>python -m compileall app engine_gateway</code></td><td>Passed</td></tr>
|
|
||||||
<tr><td>Web build</td><td><code>npm run build</code></td><td>Passed</td></tr>
|
|
||||||
<tr><td>Pages production deploy</td><td><code>wrangler pages deploy dist --project-name vignette --branch main</code></td><td>`0fe8f3cc`, source `543d48c`</td></tr>
|
|
||||||
<tr><td>Custom domain assets</td><td><code>https://vignette.chanpaca.net/</code></td><td>`index-D1Z5DH5J.js`, `index-BA9-xxfn.css`</td></tr>
|
|
||||||
<tr><td>Legacy Live2D routes</td><td><code>/live2d/mao/* / /live2d/haru/* / /live2d/live2dcubismcore.min.js</code></td><td>404</td></tr>
|
|
||||||
<tr><td>Compose</td><td><code>docker compose --env-file .env.example config --quiet</code></td><td>Passed with dummy required env</td></tr>
|
|
||||||
<tr><td>Focused E2E</td><td><code>admin + db-persistence + voice-success</code></td><td>10 passed</td></tr>
|
|
||||||
<tr><td>Admin manage-users</td><td><code>admin.spec.ts --grep manage real server-known users</code></td><td>desktop/mobile 2 passed</td></tr>
|
|
||||||
<tr><td>Full E2E</td><td><code>PLAYWRIGHT_HOST=127.0.0.1 npx playwright test</code></td><td>72 passed</td></tr>
|
|
||||||
<tr><td>Public auth discovery</td><td><code>E2E_PUBLIC_AUTH=1 npx playwright test --list --project=chromium-public-auth</code></td><td>2 tests listed</td></tr>
|
|
||||||
<tr><td>Public readiness</td><td><code>public-auth-turn.spec.ts --grep production-safe</code></td><td>1 passed</td></tr>
|
|
||||||
<tr><td>Persona auth boundary</td><td><code>GET /personas</code></td><td>verified local/public unauth 401</td></tr>
|
|
||||||
<tr><td>Public login</td><td><code>auth.spec.ts --grep public login</code></td><td>1 passed</td></tr>
|
|
||||||
<tr><td>Public runtime script</td><td><code>scripts\start-public-runtime.ps1</code></td><td>engine 9099 + API 8001 + cloudflared only</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>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>
|
|
||||||
<tr><td>Session layout/turn</td><td><code>session-layout.spec.ts desktop/mobile</code></td><td>8 passed</td></tr>
|
|
||||||
<tr><td>Voice/session focused</td><td><code>voice + voice-success + session-layout</code></td><td>15 passed</td></tr>
|
|
||||||
<tr><td>Latest focused E2E</td><td><code>learner + session-layout + settings + admin</code></td><td>37 passed</td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="section-head">
|
|
||||||
<div>
|
|
||||||
<h2>남은 리스크</h2>
|
|
||||||
<p>다음 세션 첫 작업</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid">
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag risk">blocker</span>
|
|
||||||
<h3>public `/turn` proof 없음</h3>
|
|
||||||
<p>2026-06-26 15:18 KST 기준 Google 로그인 storageState가 없어 공개 API `/turn` 실제 성공 증거가 아직 없다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag doing">ops</span>
|
|
||||||
<h3>공개 운영 프로세스 유지</h3>
|
|
||||||
<p>현재 실행 프로세스는 `127.0.0.1:9099` engine gateway, `127.0.0.1:8001` prod API, cloudflared tunnel 1개다. 재부팅 후에는 engine gateway와 prod API, cloudflared를 같이 재기동해야 한다.</p>
|
|
||||||
</article>
|
|
||||||
<article class="panel">
|
|
||||||
<span class="tag doing">check before deploy</span>
|
|
||||||
<h3>engine config 재확인</h3>
|
|
||||||
<p>E2E가 admin engine config를 건드린다. 배포 전 의도한 model/url/mode인지 다시 확인해야 한다.</p>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="section-head">
|
|
||||||
<div>
|
|
||||||
<h2>다음 명령</h2>
|
|
||||||
<p>공개 OAuth smoke</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="commands">
|
|
||||||
<pre>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</pre>
|
|
||||||
<pre>cd D:\workspace\vignette
|
|
||||||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1</pre>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="section-head">
|
|
||||||
<div>
|
|
||||||
<h2>주의</h2>
|
|
||||||
<p>작업 방식</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul class="list">
|
|
||||||
<li>`frontenddesign` 스킬은 현재 세션에 없었다. 다음 세션에서 보이면 먼저 사용하고, 없으면 `docs/DESIGN_CONCEPT.md`를 따른다.</li>
|
|
||||||
<li>학습자 홈과 active session UX는 사용자가 강하게 불만을 말한 영역이다. fake 기록/통계 금지, 0회 기록은 실제 서버 세션 0개 기반 빈 상태로 유지, active session 전체화면/무문서스크롤 유지.</li>
|
|
||||||
<li>추적 파일 기준 worktree는 깨끗하게 유지한다. `.env`, 로그, `node_modules` 같은 ignored runtime 파일은 운영/개발 상태로만 취급하고 커밋하지 않는다.</li>
|
|
||||||
<li>OAuth secret은 출력하지 말 것. 실제 secret은 환경 파일/인프라 설정에만 둔다.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="detail-block">
|
||||||
|
<h4>제품/UX 완료</h4>
|
||||||
|
<ul>
|
||||||
|
<li>학습자 홈 0회 기록 상태는 fake 기록 없이 실제 서버 세션 0개로 표시한다.</li>
|
||||||
|
<li>active session은 실제 SSE token 이벤트로 자막을 쌓는다.</li>
|
||||||
|
<li>텍스트/음성 실패 턴은 transcript와 저장소에 남기지 않는다.</li>
|
||||||
|
<li>375px급 active session viewport overflow를 보정했다.</li>
|
||||||
|
<li>직접 URL persona 우회는 DB 원본 persona가 아니면 차단한다.</li>
|
||||||
|
<li>세션 화면은 SVG persona avatar만 렌더링한다.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="detail-block">
|
||||||
|
<h4>운영/테스트 완료</h4>
|
||||||
|
<ul>
|
||||||
|
<li>seed persona 결합을 제거하고 DB persona 기반 E2E로 전환했다.</li>
|
||||||
|
<li>비개발 런타임은 dev login, seed fallback, 기본 secret, localhost CORS를 fail-closed 한다.</li>
|
||||||
|
<li>compose OAuth/CORS/OpenAI base 설정을 API 컨테이너에 명시했다.</li>
|
||||||
|
<li>persona catalog는 비로그인 401로 인증 경계를 가진다.</li>
|
||||||
|
<li>admin 모바일 overflow, AI 운영 상태 문구, global engine config 테스트 격리를 정리했다.</li>
|
||||||
|
<li>handoff, E2E README, 이 대시보드를 최신 상태로 맞췄다.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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-plan">TODO</span></div>
|
||||||
|
<div><b>LLMEngine 정규화 계약 고정</b><p>claude_cli와 Messages API 폴백이 같은 이벤트 스키마와 cost 규약을 반환하게 한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>`delta.text`, `result`, `cost` 이벤트 계약과 contract test</p></div>
|
||||||
|
<div><b>검증</b><p>동일 입력 → 두 어댑터 정상화 출력 동일성 테스트</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>상주 `claude -p` 엔진풀 실증</b><p>`--input-format stream-json` 상주 프로세스를 회기당 1회 기동하는 구조로 검증한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>TTFT p50/p95, 동시 4회기 RSS/OOM 로그</p></div>
|
||||||
|
<div><b>검증</b><p>실측 전 “빠름/검증됨” 표기 금지</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>마스킹 게이트 강제</b><p>claude_cli 경로도 Presidio/가명처리 후에만 stdin으로 들어가게 막는다.</p></div>
|
||||||
|
<div><b>산출물</b><p>엔진 어댑터 전단 마스킹 미들웨어, 우회 차단 테스트</p></div>
|
||||||
|
<div><b>검증</b><p>민감 텍스트 raw 외부 전송 경로 0개</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>P1 상담 완주 플로우</b><p>로그인, P1 선택, SSE 상담, 종료, 회기말 리뷰를 한 번에 통과시킨다.</p></div>
|
||||||
|
<div><b>산출물</b><p>turn DB 적재, session review, cost telemetry</p></div>
|
||||||
|
<div><b>검증</b><p>Playwright로 실제 로그인 → 상담 완주 → 피드백 수신</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>저항 엔진 정량 검증</b><p>공감 발화와 조언점프 발화 세트를 넣어 openness 곡선을 비교한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>공감 5턴 vs 조언 5턴 DB 쿼리 결과</p></div>
|
||||||
|
<div><b>검증</b><p>공감 시 개방도 상승, 조언점프 시 하락 증거</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>음성 왕복 1턴 PoC</b><p>Phase 1에서 음성을 완전히 뒤로 미루지 않고, 얇은 1턴 왕복 증거를 만든다.</p></div>
|
||||||
|
<div><b>산출물</b><p>STT transcript, TTS audio, voice turn log</p></div>
|
||||||
|
<div><b>검증</b><p>실제 마이크/오디오 1턴 + 실패 시 턴 미저장</p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-panel" id="panel-p2a" role="tabpanel" aria-labelledby="tab-p2a" hidden>
|
||||||
|
<div class="detail-head">
|
||||||
|
<div>
|
||||||
|
<h3>Phase 2a 권한, OAuth, 페르소나 상세</h3>
|
||||||
|
<p>음성과 섞지 않고 3역할, 권한격리, 페르소나 확장을 독립 트랙으로 닫는다.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mini-metrics"><span>주차: 9-13</span><span>우선순위: P1</span><span>IDOR 검증 필수</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-list">
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>OAuth BFF / HttpOnly 세션 완성</b><p>브라우저에 토큰을 저장하지 않는 BFF 세션을 역할 페이지 전체에 통일한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>Auth Code + PKCE, stateful session, role claim mapping</p></div>
|
||||||
|
<div><b>검증</b><p>비로그인 401, 역할 없는 접근 403, 쿠키 보안 속성 확인</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>학습자/교수자/관리자 권한 격리</b><p>RBAC와 cohort 소유권 게이트를 모든 session/review/admin API에 적용한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>권한 미들웨어, audit log, role route map</p></div>
|
||||||
|
<div><b>검증</b><p>타인 `session_id` 직접 입력 → 403 스크린샷</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>`visible_to`와 인간 권한 레이어 분리</b><p>AI 정보비대칭과 사용자 권한을 섞지 않는다. 인간 조회는 RBAC와 cohort AND 게이트다.</p></div>
|
||||||
|
<div><b>산출물</b><p>RLS 정책, enforcement middleware, 테스트 matrix</p></div>
|
||||||
|
<div><b>검증</b><p>AI-only hidden field가 인간 조회 권한을 열지 않음</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>페르소나 P2/P3 교수 검수 루프</b><p>P1 단일 시드에서 P2/P3로 확장하되 교수 검수 상태와 공개 가능 상태를 분리한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>persona review workflow, versioning, approval state</p></div>
|
||||||
|
<div><b>검증</b><p>미승인 persona는 학습자 시작 불가</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>SAML SP 스캐폴드 선반영</b><p>1차는 Google OIDC로 가되 한신대 SSO 전환 지점을 Authlib provider로 남긴다.</p></div>
|
||||||
|
<div><b>산출물</b><p>provider abstraction, SAML placeholder config</p></div>
|
||||||
|
<div><b>검증</b><p>Google OIDC 경로 회귀 없이 provider 교체 가능</p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-panel" id="panel-p2b" role="tabpanel" aria-labelledby="tab-p2b" hidden>
|
||||||
|
<div class="detail-head">
|
||||||
|
<div>
|
||||||
|
<h3>Phase 2b 음성 캐스케이드 상세</h3>
|
||||||
|
<p>음성은 “되게 보이는 UI”가 아니라, 전송, STT, EOT, 엔진, TTS, 저장까지 끊기지 않아야 한다.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mini-metrics"><span>주차: 13-17</span><span>WSS 50분</span><span>실측 전 완료 금지</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-list">
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>voice gateway WebSocket</b><p>브라우저 AudioWorklet PCM을 WSS로 보내고, TTS 오디오 청크를 내려받는다.</p></div>
|
||||||
|
<div><b>산출물</b><p>`voice.chanpaca.net` 또는 단일 도메인 WSS 경로</p></div>
|
||||||
|
<div><b>검증</b><p>50분 WSS 양방향 스트림 끊김 실측</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>Deepgram STT + EOT</b><p>interim/final transcript와 상담 튜닝 침묵 임계, semantic EOT를 연결한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>transcript timestamp, silence_ms, final segment</p></div>
|
||||||
|
<div><b>검증</b><p>침묵/말 끊김/짧은 대답 케이스 회귀</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>OpenAI TTS voice preset 추상화</b><p>persona별 voice_id와 prosody instruction을 저장하고 교체 가능하게 한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>`voice_persona_map`, provider-agnostic params</p></div>
|
||||||
|
<div><b>검증</b><p>P1/P2/P3 voice preset 분리, fallback 정상</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>barge-in / paralinguistic logging</b><p>중간 끼어들기, 침묵, 말속도 같은 음성 메타를 턴 로그와 분리 저장한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>`turn_paralinguistic` 또는 turns nullable 확장</p></div>
|
||||||
|
<div><b>검증</b><p>barge-in 케이스가 audit 가능하게 남음</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>s2s 2차 PoC 판단</b><p>캐스케이드는 1차로 확정하고, s2s는 transcript 보존/몰입감 기준으로 별도 판단한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>캐스케이드 vs s2s 비교 메모</p></div>
|
||||||
|
<div><b>검증</b><p>윤찬 결정 항목으로 기록, 람다 단독 폐기 금지</p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-panel" id="panel-p3" role="tabpanel" aria-labelledby="tab-p3" hidden>
|
||||||
|
<div class="detail-head">
|
||||||
|
<div>
|
||||||
|
<h3>Phase 3 교육용 파일럿 / 효과성 상세</h3>
|
||||||
|
<p>IRB 승인 게이트가 아니라 교육 활용 파일럿과 데이터/개인정보 게이트로 관리한다.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mini-metrics"><span>주차: 18-20+</span><span>표본: 20명</span><span>first measure</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-list">
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>20명 교육용 파일럿 운영</b><p>수련생이 실제 계정으로 세션을 완주하고 사전/사후 설문을 남긴다.</p></div>
|
||||||
|
<div><b>산출물</b><p>참여자별 session, pre/post, consent state</p></div>
|
||||||
|
<div><b>검증</b><p>20명 완주율, 중도중단 사유, 세션 로그 대조</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>효과성/KPI 측정</b><p>SUS, 자기효능감, κ/ICC, 환각률, 피드백 타당도를 파일럿에서 처음 측정한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>KPI report, metric query, anonymized export</p></div>
|
||||||
|
<div><b>검증</b><p>SUS≥80, κ≥0.6/ICC≥0.75 같은 기준 충족 여부</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>재귀학습 데이터셋 1차 산출</b><p>라벨링/검수/κ 누적은 영속 워크플로우로 관리하고 fine-tuning 여부는 별도 결정한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>export_manifest, JSONL, annotation checkpoint</p></div>
|
||||||
|
<div><b>검증</b><p>train/test 누수 없음, 원문/가명본 경계 확인</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-plan">TODO</span></div>
|
||||||
|
<div><b>개인정보/동의 감사</b><p>미성년 원본 활용동의, 가명처리, 해외 전송 고지, 삭제/철회 절차를 실제 운영 기준으로 점검한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>privacy checklist, audit log, withdrawal runbook</p></div>
|
||||||
|
<div><b>검증</b><p>동의 철회 후 접근/학습 사용 차단 재현</p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-panel" id="panel-ops" role="tabpanel" aria-labelledby="tab-ops" hidden>
|
||||||
|
<div class="detail-head">
|
||||||
|
<div>
|
||||||
|
<h3>운영 / 검증 상세</h3>
|
||||||
|
<p>실제 운영 도메인에 반영됐는지, 서버가 하나로 정리됐는지, 완료라고 말할 증거가 있는지 확인하는 탭이다.</p>
|
||||||
|
</div>
|
||||||
|
<div class="mini-metrics"><span>웹: 200</span><span>API: prod OK</span><span>Live2D: 404</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-list">
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-done">DONE</span></div>
|
||||||
|
<div><b>운영 웹 asset 확인</b><p>커스텀 도메인이 최신 JS/CSS asset을 서빙한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>`index-D1Z5DH5J.js`, `index-BA9-xxfn.css`</p></div>
|
||||||
|
<div><b>검증</b><p>`Invoke-WebRequest https://vignette.chanpaca.net/`</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-done">DONE</span></div>
|
||||||
|
<div><b>공개 API prod health</b><p>터널은 prod API 8001을 보고, API는 DB와 engine을 모두 확인한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>`environment:"prod"`, `db:true`, `engine:true`</p></div>
|
||||||
|
<div><b>검증</b><p>`https://api-vignette.chanpaca.net/health`</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-done">DONE</span></div>
|
||||||
|
<div><b>서버 정리 상태</b><p>운영 확인용 프로세스만 유지하고 임시 dev server는 내린다.</p></div>
|
||||||
|
<div><b>산출물</b><p>8001 API, 9099 engine, 20241 cloudflared</p></div>
|
||||||
|
<div><b>검증</b><p>`Get-NetTCPConnection` 대상 포트 확인</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-risk">WATCH</span></div>
|
||||||
|
<div><b>재부팅 후 런타임 재기동</b><p>Windows 재부팅/업데이트 후에는 engine gateway와 prod API, cloudflared를 같이 올려야 한다.</p></div>
|
||||||
|
<div><b>산출물</b><p>runbook 또는 scheduled task</p></div>
|
||||||
|
<div><b>검증</b><p>재기동 후 public health와 `/turn` smoke 재확인</p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-blocks">
|
||||||
|
<div class="detail-block">
|
||||||
|
<h4>검증 실행 결과</h4>
|
||||||
|
<ul>
|
||||||
|
<li>Web typecheck: `npm run typecheck` passed.</li>
|
||||||
|
<li>Backend unittest: `app.test_runtime_policy`, `app.test_session_turn_persistence`, `engine_gateway.test_gateway_model` 20 OK.</li>
|
||||||
|
<li>Python compile: `python -m compileall app engine_gateway` passed.</li>
|
||||||
|
<li>Web build: `npm run build` passed.</li>
|
||||||
|
<li>Full E2E: `PLAYWRIGHT_HOST=127.0.0.1 npx playwright test` 72 passed.</li>
|
||||||
|
<li>Latest focused E2E: learner + session-layout + settings + admin 37 passed.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="detail-block">
|
||||||
|
<h4>공개 운영 증거</h4>
|
||||||
|
<ul>
|
||||||
|
<li>Pages deploy: `0fe8f3cc`, source `543d48c`, branch `main`.</li>
|
||||||
|
<li>Custom domain assets: `index-D1Z5DH5J.js`, `index-BA9-xxfn.css`.</li>
|
||||||
|
<li>Public readiness: `public-auth-turn.spec.ts --grep production-safe` 1 passed.</li>
|
||||||
|
<li>Public login: `auth.spec.ts --grep public login` 1 passed.</li>
|
||||||
|
<li>Legacy Live2D routes: Mao/Haru/Cubism core 404.</li>
|
||||||
|
<li>Persona auth boundary: local/public unauth `GET /personas` 401.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="detail-block">
|
||||||
|
<h4>다음 실행 명령</h4>
|
||||||
|
<ul>
|
||||||
|
<li>`npx playwright codegen https://vignette.chanpaca.net/login --save-storage=.\node_modules\.tmp\public-auth.json`</li>
|
||||||
|
<li>`$env:E2E_PUBLIC_AUTH="1"`</li>
|
||||||
|
<li>`$env:E2E_PUBLIC_STORAGE_STATE=".\node_modules\.tmp\public-auth.json"`</li>
|
||||||
|
<li>`.\node_modules\.bin\playwright.cmd test e2e/public-auth-turn.spec.ts --project=chromium-public-auth`</li>
|
||||||
|
<li>`powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1`</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</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>결정 5개</span><span>조건부 GO 사유</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-list">
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-risk">DECIDE</span></div>
|
||||||
|
<div><b>s2s 2차 PoC 채택 여부</b><p>캐스케이드 1차는 확정. s2s는 몰입감과 transcript 보존 기준으로 유지/폐기를 정한다.</p></div>
|
||||||
|
<div><b>선택지</b><p>캐스케이드만 / s2s 2차 PoC 유지</p></div>
|
||||||
|
<div><b>영향</b><p>음성 아키텍처, 전송 비용, 로그 보존 방식</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-risk">DECIDE</span></div>
|
||||||
|
<div><b>fast-loop 외부 API 허용 여부</b><p>로컬 상주 분류기를 기본으로 하되, 외부 Haiku/Solar API 폴백을 허용할지 정한다.</p></div>
|
||||||
|
<div><b>선택지</b><p>로컬 전용 / 외부 폴백 허용</p></div>
|
||||||
|
<div><b>영향</b><p>데이터주권, 레이턴시, 장애 대응</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-risk">DECIDE</span></div>
|
||||||
|
<div><b>재귀학습 fine-tuning 범위</b><p>1차는 few-shot 자동갱신만 할지, fine-tuning까지 포함할지 결정한다.</p></div>
|
||||||
|
<div><b>선택지</b><p>few-shot only / fine-tuning 포함</p></div>
|
||||||
|
<div><b>영향</b><p>동의서, 데이터셋 품질, 운영 리스크</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-risk">DECIDE</span></div>
|
||||||
|
<div><b>자유연습 기본값</b><p>수정안은 기본 ON + 피드백 권장 배지 + 교수자 토글을 제안한다.</p></div>
|
||||||
|
<div><b>선택지</b><p>기본 ON / 교수자 승인 후 ON</p></div>
|
||||||
|
<div><b>영향</b><p>학습자 자율성, 평가 품질, 안전 정책</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="task-row">
|
||||||
|
<div><span class="task-status s-risk">DECIDE</span></div>
|
||||||
|
<div><b>추가 축어록 수급 규모/일정</b><p>n=1 시드의 한계를 해소하려면 추가 사례 수급 규모와 일정을 정해야 한다.</p></div>
|
||||||
|
<div><b>선택지</b><p>N건 목표/일정 확정</p></div>
|
||||||
|
<div><b>영향</b><p>평가 타당도, 페르소나 다양성, Phase 3 측정력</p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-blocks">
|
||||||
|
<div class="detail-block">
|
||||||
|
<h4>완료라고 말하는 기준</h4>
|
||||||
|
<ul>
|
||||||
|
<li>실제 로그인 기반 E2E 증거가 있어야 한다.</li>
|
||||||
|
<li>DB 쿼리나 로그로 저장/권한/비용이 확인되어야 한다.</li>
|
||||||
|
<li>시각 확인만으로 기능 완료를 선언하지 않는다.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="detail-block">
|
||||||
|
<h4>문서 기준</h4>
|
||||||
|
<ul>
|
||||||
|
<li>`MASTERPLAN`은 원본 보존 문서다.</li>
|
||||||
|
<li>`MASTERPLAN_REVISIONS`가 Phase/IRB/음성 정정 기준이다.</li>
|
||||||
|
<li>`dev_dashboard.html`은 팀장 판단용 현재판이다.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="detail-block">
|
||||||
|
<h4>운영 원칙</h4>
|
||||||
|
<ul>
|
||||||
|
<li>mock/demo 자산은 운영 번들에서 제거한다.</li>
|
||||||
|
<li>ignored runtime 파일은 커밋하지 않는다.</li>
|
||||||
|
<li>서버는 목적별 1개씩만 유지한다.</li>
|
||||||
|
<li>`frontenddesign` 스킬이 있으면 먼저 쓰고, 없으면 `DESIGN_CONCEPT`를 SSOT로 따른다.</li>
|
||||||
|
<li>UX는 `docs/DESIGN_CONCEPT.md`를 기준으로 하고, fake 기록/통계는 금지한다.</li>
|
||||||
|
<li>OAuth secret은 출력하지 않고 환경 파일/인프라 설정에만 둔다.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
<script>
|
||||||
|
const tabButtons = Array.from(document.querySelectorAll("[role='tab'][data-tab]"));
|
||||||
|
const tabPanels = Array.from(document.querySelectorAll("[role='tabpanel']"));
|
||||||
|
function activateTab(nextButton) {
|
||||||
|
tabButtons.forEach((button) => {
|
||||||
|
button.setAttribute("aria-selected", String(button === nextButton));
|
||||||
|
button.tabIndex = button === nextButton ? 0 : -1;
|
||||||
|
});
|
||||||
|
tabPanels.forEach((panel) => {
|
||||||
|
panel.hidden = panel.id !== nextButton.getAttribute("aria-controls");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tabButtons.forEach((button, index) => {
|
||||||
|
button.addEventListener("click", () => activateTab(button));
|
||||||
|
button.addEventListener("keydown", (event) => {
|
||||||
|
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
||||||
|
event.preventDefault();
|
||||||
|
let nextIndex = index;
|
||||||
|
if (event.key === "ArrowLeft") nextIndex = (index - 1 + tabButtons.length) % tabButtons.length;
|
||||||
|
if (event.key === "ArrowRight") nextIndex = (index + 1) % tabButtons.length;
|
||||||
|
if (event.key === "Home") nextIndex = 0;
|
||||||
|
if (event.key === "End") nextIndex = tabButtons.length - 1;
|
||||||
|
tabButtons[nextIndex].focus();
|
||||||
|
activateTab(tabButtons[nextIndex]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue