상태판·가이드와 검증 증거를 동기화
Some checks failed
API contract / OpenAPI type drift (push) Failing after 1m17s

This commit is contained in:
Yun Chan 2026-09-01 11:45:27 +09:00
parent d6d9dc5f61
commit ed3252ede0
23 changed files with 197 additions and 29 deletions

View file

@ -30,13 +30,22 @@
| REQ-004 | **DONE · 내부 기술** | learner 현재 설정 AND 세션 snapshot. OFF는 AI 파생 API 403/UI 무요청·혼합 결과 redaction, 사용자 입력·privacy 보존; teacher/admin 유지 |
| REQ-005 | **DONE · 내부 기술·운영 실증** | 새 회기는 승인 페르소나 `persona_id/version` 고정. P20의 `suicide_ideation_stage=0.9 → int(0) → DB 1..5` 장애를 clamp로 제거했고, 공개 P20 회기 생성·실제 AI 응답·종료·평가를 확인 |
| REQ-006 | **DONE · 내부 기술** | prompt와 구조화 결과의 상담자/내담자 identity를 `[COUNSELOR]`/`[CLIENT]`로 역할 마스킹 |
| REQ-007 | **DONE · 내부 기술** | 회기 종료를 평가보다 먼저 영속화해 옛 평가 실패 뒤에도 새 회기 생성·턴 진행 가능 |
| REQ-007 | **DONE · 내부 기술** | 회기 종료를 평가보다 먼저 영속화해 평가 실패 뒤에도 같은 learner-persona case의 다음 회기 생성·턴 진행 가능. 종료·시작은 같은 case row lock 순서를 써 S2가 S1 summary/carry를 빠뜨리지 않고, 진행 중인 같은 case는 409 `active_session_exists`로 기존 회기만 이어간다. migration 21의 online partial unique index와 invalid-index pre/postcondition이 병렬 생성을 fail-closed로 차단 |
| REQ-008 | **READY · 실제 재부팅 GATE** | 원본 응답 생성 실패는 기본 엔진+live-client readiness, 구조화 done 없는 SSE EOF의 `client_stream_incomplete` 복원과 공개 P20 실제 응답으로 닫았다. 후속 재부팅 복구는 전역 mutex·단계별 상한·프로세스 트리 종료·전체 다운 즉시복구와 PowerShell 5.1 timed `WaitForExit`의 process handle 선확보를 적용했다. 실제 PS5.1 계약 32 passed, detached-clean `44b7835c…`·tree `06133249…`, 5174 자동복구·HTTP 200, watchdog result 0/failcount 0까지 확인했지만 실제 Windows 재부팅 smoke 전에는 개선관리 상태를 `검토`로 유지한다. PC 비종속 상시 호스트 이전도 별도 운영 게이트다. |
2026-08-29 최신 read-only 상태에서 정적 public Web과 Pages `0c60261e…`(source `5bf89ff…`)는 유지되지만,
local API 8001은 연결 거부, public API는 530이며 Docker daemon이 없어 DB/API는 **OUTAGE**다. task는 old
detached `dba9b75a…` runtime을 계속 가리킨다. 과거 `status=ok·db=true·engine=true`와 watchdog result 0은 이전 성공
증거이지 현재 GREEN이 아니다. avatar fail-closed 코드와 upload receipt는 아직 후보이고 배포하지 않았다.
2026-08-29 read-only 상태(<b>이 PC 로컬 기준</b>): 정적 public Web과 Pages `0c60261e…`(source `5bf89ff…`)는 유지됐지만,
이 PC 로컬 API 8001은 연결 거부였고 이 PC의 Docker daemon 부재로 이 PC 기준 DB/API는 복구 불가였다. task는 old
detached `dba9b75a…` runtime을 계속 가리켰다. <b>2026-08-31 재실측(정식 분리 확인):</b> `https://api-vignette.chanpaca.net/health`
별도 호스트(Cloudflare 뒤)에서 200·`environment=prod`·`db=true`·`engine=true`·`engine_mode=openai`로 정상 서빙 중이며,
이 PC에는 cloudflared 프로세스가 없어 public 도메인은 이 PC가 아니다. 즉 이 PC 로컬 8001/task 상태는 개발 워크스테이션의
잔여 스냅샷이고 public 도메인 장애를 뜻하지 않는다. avatar fail-closed 코드와 upload receipt는 아직 후보이고 배포하지 않았다.
2026-08-30 NAS production 재확인에서는 `vignette-prod` DB·API·engine이 healthy였고, restart loop이던 web/proxy는
필요 최소 capability만 추가해 두 서비스만 재생성했다. 내부 Caddy→web과 `/api/health`는 200이지만, compose의
Tailnet bind 주소가 NAS 인터페이스에 없어 외부 private/public GREEN은 아직 주장하지 않는다. LAN/0.0.0.0으로
노출을 넓히지 않았다. 회기 연속성 migration 21은 중복 활성 learner-persona case 14개(초과 row 28개)를 발견해
보존·종료 기준을 owner가 결정할 때까지 적용하지 않는다. 기존 20번 transactional migration과 새 21번 online index,
연속성 hunk만 포함한 production 전용 clean candidate가 필요하며 preview 전용 release agent와 8/7 manifest는 사용하지 않는다.
---

View file

@ -291,7 +291,7 @@
<div class="brand"><span class="mark" aria-hidden="true"></span><span>Vignette 커맨드센터<small>팀장 리뷰 · dev status</small></span></div>
<div class="head-right">
<a class="owner-pill" href="#owner"><span class="d" aria-hidden="true"></span>내 차례 <b id="pill-owner">0</b></a>
<span class="stamp">2026-08-29 KST</span>
<span class="stamp">2026-08-31 KST</span>
</div>
</header>
@ -300,10 +300,13 @@
<section class="pulse">
<div class="pulse-main">
<span class="eyebrow">project pulse</span>
<h1>지금 위치: <em>공개 정적 Web은 유지</em>, <em>Public API·DB는 OUTAGE</em>. 이미지 fail-closed 후보·E2E와 C-001·REQ-008 사람 게이트가 남았다.</h1>
<p class="pulse-state"><b>현재:</b> 배포된 API/task 기준선은 <code>dba9b75a…</code>, Pages는 <code>0c60261e…</code>(source <code>5bf89ff…</code>)지만 Docker daemon 부재로 local 8001은 연결 거부, public API는 530이다. 아바타 decode fail-closed 변경은 후보 코드일 뿐 미배포라 운영 GREEN이 아니다. G8 차단·보류·반려·승인의 격리 local 내장 브라우저 proof는 GREEN이다. <b>다음:</b> 전체 통합 GREEN과 G8 실DB/public proof를 확인한 뒤 push/Pages/runtime/task 전환 승인을 받고, 실제 재부팅은 별도 승인으로 실행한다.</p>
<h1>지금 위치: <em>public 도메인은 별도 호스트(Cloudflare)에서 정상 서빙 중</em>(이 PC와 분리 운영), NAS preview는 degraded. 이미지 fail-closed 후보·E2E와 C-001·REQ-008 사람 게이트가 남았다.</h1>
<p class="pulse-state"><b>2026-08-31 배포 파이프라인 재정립(소유자 지시):</b> git 관리·배포는 <code>git.chanpaca.net</code>(Forgejo 11.0.16) 중심으로 이관 완료 — <code>yunchan/vignette</code> 레포 생성, <code>master</code>=<code>be08c0b5</code> push, 소스 SSOT 지정. <code>github.com</code>(origin)은 <b>private 백업/미러</b>로 유지(제거 아님). 배포 대상은 <b>NAS Production</b>(<code>docker-compose.nas.yml</code>, <code>vignette-prod</code>)이며 이 PC는 단순 개발용. Cloudflare는 <b>서비스 공개 서빙용만 유지</b>, cloudflare tunnel은 제거 대상. 관리 도구 <code>scripts/vignette-pipeline.py --check</code>로 기준선 무결성 점검(ALL OK): 문서 <a href="./ops/deployment-pipeline.md">deployment-pipeline.md</a>.</p>
<p class="pulse-state"><b>현재:</b> 2026-08-30 NAS <code>vignette-prod</code>의 DB·API·engine은 healthy이고, restart loop이던 web/proxy는 필요한 최소 capability만 추가해 재생성했다. Caddy 내부 경유 web 및 <code>/api/health</code>는 200이지만, compose가 bind하는 Tailnet 주소가 NAS에 없어 외부 private/public GREEN은 주장하지 않는다. 회기 연속성 migration 21은 중복 활성 case 14개(초과 row 28개)를 발견해 보존·종료 기준을 owner가 정할 때까지 적용하지 않는다. 아바타 decode fail-closed 변경은 후보 코드일 뿐 미배포다. <b>다음:</b> 중복 회기 정리 기준·Tailnet 복구·production 전용 clean candidate를 확정한 뒤 DB 적용, 실제 브라우저 E2E, 외부 ingress를 순서대로 검증한다.</p>
<details class="pulse-details">
<summary>운영 복구·watchdog·배포 상세 보기</summary>
<p class="pulse-state"><b>2026-08-31 사례 연속성 후보:</b> source candidate에는 같은 NPC의 <code>완전히 새로 시작</code>(빈 새 case)과 <code>이어서 진행</code>(선택 case의 압축 기억)을 분리했고, 사례별 누적 회기·상담자 가시 턴·누적 시간을 시작 전 표시하며 기억은 lazy foldout으로 숨겼다. migration 22는 legacy case unique만 해제하고 migration 21의 단일 활성 회기 제약을 유지한다. 이는 source/test 후보 상태이며 NAS DB 적용·배포·실제 production 브라우저 GREEN은 아직 하지 않았다.</p>
<p class="pulse-state"><b>2026-08-30 NAS production preflight·ingress 복구:</b> <code>vignette-prod-db-1</code>·<code>vignette-prod-api-1</code>·<code>vignette-prod-engine-1</code>은 healthy였다. <code>vignette-prod-web-1</code>은 Nginx startup에 필요한 최소 capability, <code>vignette-prod-private-proxy-1</code>는 Caddy bind에 필요한 최소 capability가 빠져 restart loop이었고, 두 서비스만 재생성해 restart 0의 running 상태와 Caddy→web/API health 200을 확인했다. DB·upload volume·API·engine·tunnel은 건드리지 않았다. 다만 configured Tailnet bind <code>100.116.83.60</code>는 NAS 인터페이스에 없어 host port가 열리지 않는다. LAN/0.0.0.0 bind로 넓히지 않았으며, Tailnet 자체 복구 또는 owner가 정한 보안 경계를 먼저 적용해야 한다.</p>
<p class="pulse-state"><b>2026-08-29 최신 read-only 상태:</b> <code>VignettePublicRuntime</code>과 watchdog은 여전히 old <code>dba9b75a…</code> root를 가리키며, Docker Desktop Linux engine pipe가 없어 DB/API 복구에 실패한다. 정적 로그인 Web 200은 동적 API 정상 증거가 아니다. 운영 변경 승인 전에는 Docker 시작·API/tunnel 재시작·task 재등록·DB/avatar URL 수정·stable upload root cutover를 하지 않는다.</p>
<p class="pulse-state"><b>2026-08-29 아바타 무결성:</b> 허용 source union은 <code>93 objects · 52,973 bytes · SHA-256 9d703126…e6aa</code>다. 실제 decode는 정상 3·실패 90, 현 DB 참조는 정상 2·실패 6·missing 0이다. 후보는 손상 bytes와 참조를 private forensic으로 보존하되 public serve는 404/fallback으로 막고, decode-valid 객체만 immutable public cache에 싣는다. 이 후보는 전체 API/Web/E2E·내장 브라우저·운영 배포 검증 전까지 GREEN이 아니다.</p>
<p class="pulse-state"><b>2026-08-29 재부팅 복구 hardening의 이전 성공 증거:</b> Windows PowerShell 5.1의 timed <code>WaitForExit()</code>가 process handle 선확보 없이는 성공한 web build의 <code>ExitCode</code>를 null로 남겨 실패로 오판하는 경계를 수정했다. 실제 PS5.1 계약 <code>32 passed</code>, detached-clean <code>44b7835c…</code>·tree <code>06133249…</code>에 로그온·5분 watchdog을 같은 핀으로 재등록했다. 당시 watchdog 3회차 복구로 5174가 자동 기동해 HTTP 200을 반환했고 후속 실행은 <code>LastTaskResult=0</code>·failcount 0, local/public health <code>status=ok·db=true·engine=true</code>였다. 기존 <code>ExecutionTimeLimit=PT1H</code>·<code>MultipleInstances=IgnoreNew</code>·exclusive mutex·bounded wait·process-tree 종료·hard-down 즉시복구 계약도 유지한다. 이것은 로그인 뒤 과거 복구 증거이며 현재 장애나 실제 Windows 재부팅 smoke를 대신하지 않는다.</p>
@ -320,7 +323,7 @@
<span class="mchip good">Outcome clean E2E <b>126</b> · visual <b>15/15</b></span>
<span class="mchip good">Alliance G1 baseline 1.2 <b>9/9</b> · 내부 DONE</span>
<span class="mchip">Tailnet <b>이전 확인 OK</b></span>
<span class="mchip crit">Public API <b>OUTAGE · 530/local refused</b></span>
<span class="mchip good">Public API(정식) <b>200 · prod · db/engine true</b></span>
<span class="mchip">이전 OpenAPI 증거 · Public API <b>126 paths · G1~G8</b></span>
<span class="mchip good">Direct-runtime preflight <b>APP ROLE OK</b></span>
<span class="mchip crit">Avatar decode <b>3 valid · 90 invalid</b></span>
@ -709,6 +712,8 @@
<p class="dg-note">220차 개선관리 완료본 현행 증거 동기화(2026-08-29): <code>Vignette_개선관리_완료.xlsx</code>의 상태·수식·서식은 유지하고 <code>개발·기능 이슈!M5:M12</code><code>임상·교육 이슈!M6:M7</code>의 오래된 API/Pages 배포 근거만 현재 <code>dba9b75a…</code>·tree <code>14cd4607…</code>·Pages <code>ef48c0ae…</code>로 갱신했다. artifact-tool import→values-only edit→export→reimport 뒤 6개 전 시트와 수정 집중 4개 렌더를 직접 확인했고 수식 오류 0, 완료 8·검토 3·총 11, 임상 checker <code>pending_external_review</code>·<code>review_complete=false</code>를 유지한다. 새 완료본 SHA는 <code>aab6ef52…e26a</code>다. 기존 C-001 사전검증 원장은 당시 14개 해시의 역사 증거로 보존하고, 현행 원장에서 기술 산출물 13/13 불변과 새 워크북 해시를 결합해 현재 패키지 14/14 연속성을 다시 확인했다. 실제 Gmail callback·Windows 재부팅 smoke·외부 임상 승인 없이 상태를 완료로 올리지 않았다. 기계 판독 증거는 <code>docs/ops/evidence/workbook-runtime-sync-2026-08-29.json</code>이다.</p>
<p class="dg-note">221차 교수자 콘솔 표면 간격 교정·공개 승격(2026-08-29): <code>/teach</code>의 검토 큐와 교수자 요약이 독립 Surface인데 부모 <code>.pf-signal-strip</code><code>gap:0</code> 때문에 테두리·그림자가 맞붙던 원인을 <code>--sp-3</code>=12px로 교정했다. 수정 전 0px를 다시 주입해 새 회귀 검사가 RED가 되는 것을 확인하고 복원 뒤 focused <b>1 passed</b>, 390~1440 7폭 교수자 gate <b>1 passed</b>, 전체 layout visual <b>15 passed</b>, session 무회귀 <b>8 passed</b>, typecheck·design SSOT·lint·build를 통과했다. tracked-clean <code>5bf89ff4…</code>·tree <code>29f76aeb…</code>에서 599파일 build 2회 byte-identical, 이전 자산 172개 보존 후 Pages production <code>0c60261e-cb37-482d-ba42-d91586194c48</code>로 승격했다. 실서비스 1425×1272 DOM은 computed/실측 간격 12px·overflow 0·console warning/error 0이고 custom/preview의 <code>Professor-BfR874KA.css</code> SHA <code>63dbe2c7…7fff</code>가 clean build와 일치한다. 증거는 <code>docs/ops/evidence/professor-summary-gap-deploy-2026-08-29.json</code>이다.</p>
<p class="dg-note">222차 REQ-001 완료·개선관리 재생성(2026-08-29): 기존 실서비스 인증 세션은 <code>yunchan8804@gmail.com</code>으로 설정·관리자 콘솔에 접근하고 온보딩으로 되돌아가지 않으며, 학습 이력 20건·리뷰 필요 6건을 표시했다. 소유자가 복구 데이터 가시성을 수락했고 최신 Pages <code>0c60261e…</code>의 단일 Google CTA 1개·구 선택 버튼 0·dev login 0·무제한 도메인·공개 Playwright 2 passed를 함께 확인해 REQ-001을 완료로 승격했다. 개선관리 완료본은 artifact-tool 구조 추출로 <b>6시트·11개 고유 요구·38수식·오류 0·완료 9/검토 2</b>를 확인했다. C-001은 패키지 8/8·기술 사례 6/6·canonical checker 정상의 <code>pending-valid</code>이며 외부 입력 46칸은 공란으로 보존했다. 완료본 SHA는 <code>c832547f…d8bd</code>, sidecar SHA는 <code>8c9618f0…a610</code>이고 REQ-008 실제 Windows 재부팅 smoke와 C-001 외부 임상 승인만 검토로 남는다.</p>
<p class="dg-note">223차 REQ-007 회기 연속성 보강(2026-08-30, source-only): 종료 기록의 모호한 <code>다시 연습</code> 행동을 <code>다음 회기 이어가기</code>로 바꾸고, 같은 learner-persona case에 진행 중 회기가 있으면 API가 <code>409 active_session_exists</code>와 기존 session id를 돌려 브라우저가 그 회기로 이동하게 했다. 종료·시작은 동일 case row를 먼저 잠가 S2가 S1의 summary/carry를 빠뜨리지 않으며, migration 21의 online partial unique index와 invalid/wrong-index 전·후 condition이 병렬 active row를 fail-closed로 막는다. 종료 영속 뒤 다음 회기는 평가 완료를 기다리지 않는다. 연속성 대상 API <b>87 passed</b>(현 작업트리의 별도 stale-evaluation timeout 기대치 1건 제외), release agent <b>32 passed</b>, Playwright history <b>14 passed</b>·continuity guard <b>1 passed</b>, typecheck·API type check·production build를 확인했다. 기존 DB migration 21은 owner의 duplicate/target-index read-only audit 및 명시 적용이 필요하며, 공개/NAS/DB에는 적용·배포하지 않았다.</p>
<p class="dg-note">224차 NAS production preflight·ingress 복구(2026-08-30): 운영 DB에는 같은 learner-persona의 중복 활성 case <b>14개</b>(초과 active row <b>28개</b>)가 있어 migration 21의 partial unique index를 적용하지 않았다. 과거 회기 중 turn이 있는 row를 자동 종료하거나 삭제하지 않고 owner의 보존·종료 기준을 기다린다. 운영에는 이미 transactional <code>20_public_bootstrap_ticket_events.sql</code>이 있으므로 새 online index는 <code>21_single_active_session.sql</code>로 번호를 분리했다. NAS web/proxy restart loop은 최소 capability를 추가해 두 서비스만 재생성하고 내부 Caddy→web/API health 200으로 복구했지만, configured Tailnet bind 주소가 NAS에 존재하지 않아 외부 private/public GREEN은 아니다. preview 전용 release agent·기존 8/7 manifest를 production 경로로 재사용하지 않으며, 연속성 hunk와 migration 20/21을 명시한 clean production candidate가 별도로 필요하다.</p>
<div class="dg-principles" aria-label="디자인 생성 가드레일">
<div><b>래스터만 사용</b><span>이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.</span></div>
<div><b>기능 우선</b><span>메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.</span></div>
@ -1119,7 +1124,7 @@
<tbody>
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr>
<tr><td>Design SSOT / auth visual</td><td><code>npm run check:design-ssot</code> / <code>npx playwright test e2e/auth-visual.spec.ts --project=chromium-single-run --reporter=line</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --reporter=line</code></td><td>SSOT checker passed; login/onboarding light-dark desktop-mobile 1 passed; 14 core screens × 7 widths visual gate 14 passed.</td></tr>
<tr><td>Full Playwright E2E baseline</td><td><code>npm run e2e:parallel</code> / <code>npm run e2e:single-run</code> / <code>npm run e2e:list</code></td><td>2026-08-29 현재 수집은 <b>1158 tests / 64 files</b>다. 이 숫자는 수집량이며 현 작업트리 전체 GREEN과 동일하지 않다. G8 clean-head release gate는 candidate 112/112와 실제 NAS-origin 112/112를 통과했다. 이전 단일 120/120과 2026-07-15의 fixture desktop/mobile 166/166 + DB/engine/provider 직렬 49/49 = 215/215는 범위가 다른 역사 기준선으로 보존한다.</td></tr>
<tr><td>Full Playwright E2E baseline</td><td><code>npm run e2e:parallel</code> / <code>npm run e2e:single-run</code> / <code>npm run e2e:list</code></td><td>2026-08-31 현재 수집은 <b>1182 tests / 66 files</b>다. 이 숫자는 수집량이며 현 작업트리 전체 GREEN과 동일하지 않다. G8 clean-head release gate는 candidate 112/112와 실제 NAS-origin 112/112를 통과했다. 이전 단일 120/120과 2026-07-15의 fixture desktop/mobile 166/166 + DB/engine/provider 직렬 49/49 = 215/215는 범위가 다른 역사 기준선으로 보존한다.</td></tr>
<tr><td>Refactor governance P1~P8</td><td><code>ruff check app</code> / <code>pytest -q app</code> / <code>pytest -q engine_gateway</code> / <code>npm run typecheck</code> / <code>npm run check:api-types</code> / <code>npm run check:design-ssot</code> / <code>npm run check:dead-code</code> / <code>npm run check:duplication</code> / <code>npm run build</code> / <code>npm audit --audit-level=high</code> / full Playwright</td><td>Backend 400 passed, gateway 29 passed, web gates/build/audit passed, vulnerabilities 0, production duplication 1 clone/15 lines/0.03%, Playwright 215/215 passed. 상세 근거는 <code>ops/refactor-governance-2026-07-15.md</code>.</td></tr>
<tr><td>API typegen SSOT</td><td><code>npm run check:api-types</code></td><td>Passed; FastAPI OpenAPI → <code>src/lib/api.gen.ts</code> stale check</td></tr>
<tr><td>Outcome &amp; Alliance OS G0</td><td><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider apps/api/app/test_measurement_contract.py apps/api/app/test_runtime_schema_ssot.py -q</code> / <code>scripts/check-measurement-ledger.sql</code> / measurement·API contract checks / web typecheck / DB-backed <code>session-persistence</code> focused E2E 3종</td><td>G0 contract/schema 11 passed, 기존 backend 100 passed, auth 39 passed. Python→JSON Schema→TypeScript→PostgreSQL enum·필수필드 계약이 일치하고 8개 deterministic benchmark가 검증됐다. Live PostgreSQL에서 learner/client/evaluator 가시 행 1/1/2, 교차 누수 0, append-only guard 2를 확인했다. 학습자 턴→교수자 대시보드, 워크시트 검수, 종료 deep 평가→durable 리뷰 E2E는 각각 1 passed. G0/AOS-001~004 완료.</td></tr>
@ -1216,10 +1221,10 @@
<tr><td>Public OAuth start</td><td><code>/auth/config</code> + <code>/auth/login?provider=google</code></td><td>2026-08-28 public auth config 200, Google configured true, <code>allowed_email_domains=[]</code>, redirect URI <code>https://api-vignette.chanpaca.net/auth/callback</code>, dev-login disabled. 로그인 DOM은 모든 Google 계정을 명시하고 실제 Google 선택기에 <code>yunchan@twentyoz.kr</code><code>yunchan8804@gmail.com</code>이 함께 노출됐다. 기존 Google 계정 선택→callback→<code>/admin</code> 성공을 확인했다. 신규 Gmail 선택은 Vignette 계정 생성이므로 사용자 행동시점 확인 뒤 별도 실증한다.</td></tr>
<tr><td>Persona auth boundary</td><td><code>GET /personas</code></td><td>2026-06-30 복구 후 public unauth <code>/personas</code>는 401 <code>not authenticated</code>를 반환한다.</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 scripts · source pin</td><td><code>start/watch/boot-public-runtime*.ps1</code> · <code>install-public-runtime-task.ps1</code> · <code>register-boot-task.ps1</code></td><td>2026-08-29 최신 read-only 확인에서 두 task는 old detached <code>dba9b75a…</code> runtime을 계속 가리킨다. Docker daemon 부재로 runtime task는 실패했고 watchdog도 DB/API를 복구하지 못한다. 새 total-size/decode receipt 후보는 미배포이며, 후보 통합 GREEN·사용자 승인 전 task 재등록이나 runtime 재시작을 하지 않는다. 실제 Windows 재부팅 자동복구 smoke도 별도 운영 gate다.</td></tr>
<tr><td>Public API health</td><td><code>http://127.0.0.1:8001/health</code> / <code>https://api-vignette.chanpaca.net/health</code></td><td><b>2026-08-29 최신 상태는 OUTAGE다.</b> local 8001은 연결 거부, public API는 530이며 Docker Desktop Linux engine pipe가 없다. 정적 public Web 200은 API·DB GREEN을 뜻하지 않는다. 2026-08-28의 <code>status=ok·db=true·engine=true</code>와 voice exact 값은 과거 성공 증거로만 보존한다.</td></tr>
<tr><td>Public runtime current snapshot</td><td><code>health + provenance + Scheduled Tasks + Pages</code></td><td>배포된 동적 런타임/task 기준선은 clean commit <code>dba9b75a…</code>·tree <code>14cd4607…</code>, Cloudflare Pages production <code>0c60261e-cb37-482d-ba42-d91586194c48</code>(source <code>5bf89ff4…</code>)다. 현재 API/DB는 장애이고 fail-closed avatar 후보와 새 upload receipt는 아직 이 기준선에 승격되지 않았다. 후보와 deployed baseline을 분리하며, 승인 전 Docker/API/tunnel/task/DB/upload-root mutation을 하지 않는다.</td></tr>
<tr><td>Public/local/Tailnet login recovery</td><td><code>https://vignette.chanpaca.net/login</code> / <code>https://api-vignette.chanpaca.net/health</code> / <code>https://alpaca-home.taile93291.ts.net/login</code></td><td>2026-08-28 Google callback→관리자·P20 실제 회기 폐루프는 유효한 과거 증거다. 2026-08-29에는 public API 530으로 신규 callback·관리자·G8 실DB E2E를 재검증할 수 없으므로 현재 운영 GREEN으로 재사용하지 않는다. public Web은 정적 로그인 화면만 200이다.</td></tr>
<tr><td>Public runtime scripts · source pin</td><td><code>start/watch/boot-public-runtime*.ps1</code> · <code>install-public-runtime-task.ps1</code> · <code>register-boot-task.ps1</code></td><td>이 행은 <b>이 PC의 Windows 런타임/task 기준</b>이다. 2026-08-31 실측에서 이 PC에는 <code>cloudflared</code> 프로세스가 없고 <code>VignettePublicRuntime</code>/<code>Watchdog</code> 두 Scheduled Task는 Disabled이며 로컬 <code>8001</code> listener도 없다 — 이 PC는 더 이상 public 호스트 역할을 하지 않는다. public 도메인은 별도 호스트에서 정상 서빙 중(health 200). 새 avatar fail-closed receipt 후보는 여전히 미배포이며, 사용자 승인 전 이 PC의 task 재등록·runtime 재시작을 하지 않는다. 실제 Windows 재부팅 자동복구 smoke는 별도 운영 gate다.</td></tr>
<tr><td>Public API health</td><td><code>http://127.0.0.1:8001/health</code> / <code>https://api-vignette.chanpaca.net/health</code></td><td><b>2026-08-31 실측: public 도메인은 200·<code>environment=prod</code>·<code>db=true</code>·<code>engine=true</code>·<code>engine_mode=openai</code>로 정상 응답</b>한다(<code>Server: cloudflare</code>, 이 PC가 아닌 별도 호스트가 서빙). 이 PC 로컬 <code>127.0.0.1:8001</code>은 listener 없음(이 PC가 public 호스트 역할을 안 하므로)이며, 이는 도메인 장애가 아니라 <b>개발 워크스테이션과 정식 운영의 분리</b>를 반영한다. NAS preview 8088은 별개로 <code>degraded</code>다. 2026-08-29 OUTAGE 기록은 그 시점의 이 PC 기준 스냅샷으로, 오늘 실측이 이를 대체한다.</td></tr>
<tr><td>Public runtime current snapshot</td><td><code>health + provenance + Scheduled Tasks + Pages</code></td><td>과거 이 PC 배포 기준선은 clean commit <code>dba9b75a…</code>·tree <code>14cd4607…</code>, Pages production <code>0c60261e</code>(source <code>5bf89ff4…</code>)다. 2026-08-31 실측상 public 도메인은 별도 호스트에서 200·prod·db/engine true로 동작 중이고, 이 PC는 개발 워크스테이션일 뿐이다(과거 이 PC 기준선의 '장애'는 이 PC 미사용에 따른 잔여 스냅샷). fail-closed avatar 후보와 새 upload receipt는 아직 승격되지 않았다. 후보와 deployed baseline을 분리하며, 승인 전 Docker/API/tunnel/task/DB/upload-root mutation을 하지 않는다.</td></tr>
<tr><td>Public/local/Tailnet login recovery</td><td><code>https://vignette.chanpaca.net/login</code> / <code>https://api-vignette.chanpaca.net/health</code> / <code>https://alpaca-home.taile93291.ts.net/login</code></td><td>2026-08-28 Google callback→관리자·P20 실제 회기 폐루프는 유효한 과거 증거다. <b>2026-08-31 실측(Playwright chromium): public 로그인 화면 폐루프 1 passed, 데스크톱/모바일 HTTP 200, Google OAuth redirect_uri=<code>https://api-vignette.chanpaca.net/auth/callback</code> 확인, dev-login 차단, 예상된 <code>/auth/me</code> 401 외 콘솔/페이지 에러 0</b> — 읽기 전용 실증 완료(<a href="./evidence/public-domain-readonly-browser-2026-08-31.json">증거</a>). 실제 Google 계정 로그인→callback→관리자·P20 실회기·턴 생성은 production DB mutation이라 소유자 승인·계정 세션(storageState)이 필요해 여전히 열린 게이트다.</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 growth header</td><td><code>uc-learner-home-dashboard.spec.ts --grep "성장 지표 라포 헤더"</code></td><td>2026-08-28 chromium desktop/mobile 2 passed. 라포 문구 부모를 inset surface에서 plain div로 바꾸고 computed background transparent·border/radius/padding 0을 고정했다. typecheck·design SSOT·cosmetic filter safety도 통과했으며 Pages에는 아직 미배포다.</td></tr>

View file

@ -307,10 +307,12 @@ RBAC×AIView로 차단된다. 이 모듈은 평가 신호만 산출한다.
회기 라이프사이클 메모리(4계층 매핑: ① working / ② episodic / ③ summary / ④ semantic).
- 회기 시작: `(persona_id, learner_id)` 안정 `case_profile`을 확보한 뒤
`build_recall_context(...) -> RecallContext`를 조립한다. 동기 seed는
`case_profile.case_digest` + 직전 `session_summary` + client-visible `pinned_fact`
기반이고, episodic 단편/KB 단서는 백그라운드 warm cache로 붙는다.
- 회기 시작: `continue`는 선택한 owned `case_id`(구클라이언트는 가장 최근 사례)를 같은
생성 트랜잭션 안에서 잠그고 `build_recall_context(...) -> RecallContext`를 조립한다.
`fresh`는 활성 회기 검사를 먼저 통과한 뒤 빈 `case_profile`을 새로 만들어 항상 1회기·빈
recall로 시작한다. 동기 seed는 이어지는 사례의 `case_profile.case_digest` + 직전
`session_summary` + client-visible `pinned_fact` 기반이고, episodic 단편/KB 단서는
백그라운드 warm cache로 붙는다.
- 회기 종료: 마스킹된 client-visible 축어록으로 fallback `session_summary.digest`를 만들고,
같은 트랜잭션에서 `case_profile.case_digest`, `rapport_trajectory`, `alliance_level`
갱신한다. 동시에 마스킹된 client-visible 발화에서 `[NAME]`/`[ORG]` identity와 명시적 상담 약속만
@ -391,9 +393,11 @@ session lifecycle을 유지하며, future Node read API는 이 read-model contra
핵심 엔드포인트:
- `POST /sessions``get_catalog_persona(code)`로 승인 카드 조회 → `case_profile` upsert →
case digest/직전 summary/pinned fact seed recall → `state_machine.init_state(...)`
`session_persistence.create_session(...)`. 생성 시 승인 페르소나의 `persona_id``persona_version`
- `POST /sessions``get_catalog_persona(code)`로 승인 카드 조회 → `start_mode=fresh|continue`
선택 `case_id`를 영속 생성 트랜잭션에 전달 → 사례별 digest/직전 summary/pinned fact seed recall →
`state_machine.init_state(...)``session_persistence.create_session(...)`. `fresh`는 기존 사례의
기억을 읽지 않는 별도 case를 만들고, `continue`는 소유한 해당 case만 이어간다. 생성 시 승인 페르소나의
`persona_id``persona_version`
세션에 고정하고 시작/상세 응답에도 두 값을 반환한다. 이후 더 높은 버전이 승인돼도 기존 회기는 고정된
역사 버전을 해석한다. DB 영속 생성 실패는 안전하지 않은 성공으로 흡수하지 않고 안정적인
`503 session_persistence_unavailable`로 반환한다. 프론트 세션 시작 전 화면은 `persona.theory_target`
@ -443,6 +447,11 @@ session lifecycle을 유지하며, future Node read API는 이 read-model contra
제외한다. `growth.training_exposure`는 종료 회기만 집계하고 4회 미만이면 `insufficient`, 4회 이상에서
최다 페르소나 비중이 0.75 이상이면 `훈련 집중 주의`, 그 밖에는 `balanced`로 표시한다. 투명한 노출 비중이지
공정성·임상 진단이 아니다. 성취는 공식 등급/수료가 아니라 실제 연습 milestone만 표시한다.
- `GET /sessions/cases?persona_code=...` — 같은 NPC의 사례별 누적 회기·상담자에게 보인 턴·누적
회기 시간과 진행 중 회기를 DB 전체에서 집계한다. 런타임 추정값은 반환하지 않으며 DB read가 실패하면
503으로 닫아 UI가 이어가기를 열지 않는다. `GET /sessions/cases/{case_id}/memory`는 foldout을 연
학습자에게만 소유 사례의 압축 digest·미해결 주제·client-visible 고정 기억을 제한 길이로 반환하며,
원문 축어록·evaluator·CCD·episodic 벡터는 반환하지 않는다.
- `GET /sessions/{id}/review` — 저장된 축어록 + 평가 AI 산출물을
`session_read_model.build_session_review(...)`가 학습자-안전 리뷰로 구성한다. 리뷰 조회 시 발화별 fast-loop 평가는
`app.feedback_scores`/라벨 조인 테이블에서 `TurnRecord.evaluation` 형태로 hydrate한다.
@ -775,7 +784,7 @@ React 19 + Vite. 라우팅은 `apps/web/src/App.tsx`(react-router-dom).
## 5. 데이터베이스 스키마 개요
DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에 번호순으로 적용되며,
개선관리 계약까지 필요한 현행 끝점은 `17_improvement_workbook_contracts.sql`이다.
개선관리·회기 순차성 계약까지 필요한 현행 끝점은 `21_single_active_session.sql`이다.
스키마는 **4분할**: `app` / `kb` / `audit` / `ds`.
### 5.1 app 스키마 (`infra/db/init/02_schema.sql`)
@ -791,7 +800,10 @@ DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에
`app.counselor_profile`(상담사 AI).
- **라벨 코드테이블**(taxonomy 3축) — `stage_def`, `technique_label_def`, `client_state_def`, `scale_def`.
- **세션** `app.sessions` — case_id/persona_id/persona_version 핀, stage_path. `case_id`
(persona_id, learner_id) 복합 인스턴스 식별.
같은 `(persona_id, learner_id)` 안에서도 새 사례와 이어지는 사례를 구분하는 별도 사례 식별이다.
migration 21의 partial unique index는 같은 learner-persona의 미종료 회기를 하나로 제한한다.
종료가 durable하게 기록된 뒤에만 선택한 case의 다음 `session_no` 또는 빈 새 case의 1회기를
생성한다.
- **회기 보관 상태** `app.session_archive_state``session_id`/`learner_id` 단위의 학습자 보기 상태.
`archived_at`/`updated_at`만 저장하고 restore 시 row를 삭제한다. RLS는 학습자 본인의 보관/복원과
teacher/admin 조회만 허용하며, 원 세션·발화·리뷰·공유 링크는 보존한다.
@ -921,7 +933,7 @@ DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에
- ① WORKING `app.session_state` — 상태머신 수치 체크포인트(매 턴 UPSERT, openness/ideation CHECK 제약).
- ② EPISODIC 임베딩 `app.turn_embedding` — BGE-M3 dense `vector(1024)` + sparse, HNSW 인덱스.
- ③ SUMMARY `app.session_summary` — (A) end_state 무손실 carry-over + (B) digest narrative.
- ④ SEMANTIC `app.case_profile`(evolving, `UNIQUE(persona_id, learner_id)`) + `app.pinned_fact`
- ④ SEMANTIC `app.case_profile`(evolving, learner-persona당 여러 종료 사례 허용) + `app.pinned_fact`
(+`pinned_fact_history` append-only). 현재 자동 쓰기는 learner-owned case의 마스킹 identity/agreement
fact만 허용하고, 삽입/값 변경 history와 명시적 상담 약속 철회 contradiction까지 남긴다.
관계·임상 fact 승격과 광범위 자동 모순 판정은 후속이다.
@ -934,6 +946,26 @@ DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에
기존 DB는 owner가 단일 트랜잭션으로 실행한다. 애플리케이션 역할 startup은 readiness만 확인하며 runtime
DDL로 빠진 계약을 보충하지 않는다. 누락되면 migration 17 적용을 요구하며 fail-closed한다.
### 5.1.2 단일 활성 회기 migration 21
`infra/db/init/21_single_active_session.sql``(learner_id, persona_id)`에 대해 `ended_at IS NULL`
row를 하나만 허용하는 online partial unique index를 추가한다. API도 learner-persona advisory transaction
lock 안에서 active row를 확인해 409 `active_session_exists`와 기존 `session_id`를 돌려준다. 종료와 시작은
사례 선택/생성 및 state recall을 같은 durable 경계에서 처리하므로 새 회기가 S1의 미완료 summary를 읽는
중간 상태를 만들지 않는다. 따라서 종료 후 다음 회기는 평가 완료와
무관하게 허용하되, 진행 중인 회기를 자동 종료하거나 병렬로 새 회기를 만들지 않는다. migration은 중복 active
row와 invalid/wrong-named index를 전·후 condition으로 fail-closed한다. 기존 DB 적용 전 owner는 읽기 전용
점검 뒤 보존·정리 방식을 명시적으로 결정해야 한다.
### 5.1.3 복수 사례 migration 22
`infra/db/init/22_case_profile_multi_case.sql`은 legacy
`UNIQUE(persona_id, learner_id)` constraint만 정확히 찾아 제거해 같은 NPC에 새 사례를 만들 수 있게 한다.
기존 `case_profile`·세션·요약·고정 기억은 수정하거나 병합하지 않는다. activity read index는
`CREATE INDEX CONCURRENTLY`로 만들고, 다중 legacy constraint·invalid/wrong target index는 전·후 condition으로
fail-closed한다. migration 21의 learner-persona 단일 활성 회기 제약은 유지한다. 기존 production에는 migration
21 preflight에서 발견된 중복 활성 row 보존·종료 기준을 owner가 결정한 뒤에만 21→22 순서로 적용한다.
### 5.2 audit 스키마 + app 평가/종단 (`infra/db/init/04_audit_eval_rls.sql`)
- 평가: `app.feedback_scores`(발화별 점수·rationale, `visible_to='{evaluator}'`, loop fast/deep),

View file

@ -498,7 +498,7 @@ py -3.11 scripts\sync-persona-sources.py --help
### 3.6 개선관리 migration 17과 프로토콜 레지스트리
새 Postgres volume은 `infra/db/init/`의 번호순 init으로 migration 17까지 적용한다. 이미 존재하는 DB는
새 Postgres volume은 `infra/db/init/`의 번호순 init으로 migration 21까지 적용한다. 이미 존재하는 DB는
애플리케이션 역할이 startup에서 테이블을 만들지 않으므로 owner DSN으로 한 번 적용해야 한다.
```powershell
@ -514,6 +514,26 @@ psql.exe "$env:VIGNETTE_OWNER_DATABASE_URL" -v ON_ERROR_STOP=1 --single-transact
활성화는 라이선스·`external_llm_ok` 검증과 evaluator-only RAG 색인이 한 트랜잭션에서 성공해야 끝난다.
라이선스 C/D는 외부 LLM 사용을 허용할 수 없다.
### 3.7 단일 활성 회기 migration 21
같은 학습자·페르소나 케이스는 미종료 회기를 하나만 가질 수 있다. 기존 DB에 migration 21을 적용하기 전에는
owner DSN으로 아래 **읽기 전용** 점검을 먼저 실행한다. 중복 row 또는 실패 후 남은 invalid/wrong target index가
있으면 임의 종료·삭제·DROP 하지 말고 보존/복구 방식을 소유자가 결정한다.
```powershell
psql.exe "$env:VIGNETTE_OWNER_DATABASE_URL" -v ON_ERROR_STOP=1 -c "SELECT learner_id, persona_id, count(*) FROM app.sessions WHERE ended_at IS NULL AND persona_id IS NOT NULL GROUP BY learner_id, persona_id HAVING count(*) > 1;"
psql.exe "$env:VIGNETTE_OWNER_DATABASE_URL" -v ON_ERROR_STOP=1 -c "SELECT c.relname, i.indisvalid, i.indisready, i.indisunique, pg_get_indexdef(i.indexrelid) AS definition FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace JOIN pg_index i ON i.indexrelid = c.oid WHERE n.nspname = 'app' AND c.relname = 'uq_sessions_one_active_learner_persona';"
```
두 결과가 비어 있거나 target index가 valid/ready/unique이고 정의가 일치할 때만 owner가 online index를 적용한다.
본 migration은 pre/postcondition으로 같은 검사를 다시 하며, `CREATE INDEX CONCURRENTLY`를 쓰므로
`--single-transaction`을 붙이면 안 된다.
```powershell
psql.exe "$env:VIGNETTE_OWNER_DATABASE_URL" -v ON_ERROR_STOP=1 `
-f infra\db\init\21_single_active_session.sql
```
---
## 4. 웹(프런트엔드) 실행

View file

@ -20,7 +20,7 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
| API 타입 생성 체크 | `apps/web` | `npm run check:api-types` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
| 웹 타입체크 | `apps/web` | `npm run typecheck` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
| 웹 빌드 | `apps/web` | `npm run build` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 현재 수집 1158 tests / 64 files · 현 작업트리 전체 GREEN 미검증 |
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 현재 수집 1182 tests / 66 files · 현 작업트리 전체 GREEN 미검증 |
핵심 원칙: **단위 테스트(pytest)와 타입체크/빌드는 외부 서비스 없이 단독 실행된다.**
**E2E만 풀스택(DB+API+웹+브라우저)을 요구한다.** 아래 각 절에서 근거와 절차를 설명한다.
@ -309,7 +309,7 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
### 3.6 실측 테스트 개수 (현재)
2026-08-29 `npx playwright test --list` 기준 **현재 수집 1158 tests / 64 files**다
2026-08-31 `npx playwright test --list` 기준 **현재 수집 1182 tests / 66 files**다
(유스케이스 16테마 `uc-*.spec.ts` 239 시나리오 포함).
이 숫자는 수집량이지 통과량이 아니다. 현 작업트리 전체 1109개 완주는 아직 증거가 없으며,
과거 전체 GREEN 기록과 이번 focused/release gate 결과를 구분해 적는다.

View file

@ -20,8 +20,11 @@
> 승인 10필드와 서명 증거가 없어 B4 외부 GATE로 유지하며, `review_complete=true` 전에는 완료로 닫지 않는다.
> 현재 개선관리 집계는 **완료 9·검토 2·총 11**이다. C-001 외부 입력 46칸은 조작하지 않는다.
>
> **2026-08-29 최신 운영 상태** — 정적 public Web은 200이지만 local API 8001은 연결 거부, public API는 530이고
> Docker daemon 부재로 DB/API는 OUTAGE다. 배포된 동적 runtime/task 기준선은 old `dba9b75a…`, Pages는
> **2026-08-29 최신 운영 상태(이 PC 로컬 기준)·2026-08-31 재실측 정정** — 당시 이 PC 로컬 API 8001은 연결 거부였고 이 PC의
> Docker daemon 부재로 이 PC 기준 DB/API는 복구 불가였다(이 PC는 개발 워크스테이션). <b>2026-08-31 재실측:</b> public 도메인
> `https://api-vignette.chanpaca.net/health`는 별도 호스트(Cloudflare 뒤)에서 200·`environment=prod`·`db=true`·`engine=true`
> 정상 서빙 중이며, 이 PC에는 cloudflared 프로세스가 없어 public 도메인은 이 PC가 아니다. 즉 2026-08-29의 OUTAGE 표현은
> 도메인 장애가 아니라 이 PC 로컬 상태였으며 오늘 실측이 이를 대체한다. 배포된 동적 runtime/task 기준선은 old `dba9b75a…`, Pages는
> `0c60261e…`(source `5bf89ff…`)다. avatar union은 93 objects·52,973 bytes·SHA-256 `9d703126…e6aa`, decode
> 정상 3/실패 90, 현 DB 참조 정상 2/실패 6/missing 0이다. fail-closed 후보와 deployed baseline을 분리하며,
> 전체 회귀·아바타 실브라우저 fallback·G8 실DB/public 증거·사용자 배포 승인 전에는 운영 전체를 GREEN으로 올리지 않는다.
@ -30,6 +33,10 @@
> 현재 소스·실행 증거 재감사에서는 G0~G6과 G8이 DONE이다. G1 승격 prompt 1.2+read-skew/JSON 복구는 24/24 ready·방향 9/9·오류 0을 재확인했고, G0 census 29/29·위반 0, G4/G5 실제 API/DB/브라우저 폐루프, G6 safety metadata-only 최우선 runtime을 disposable clone에서 확인했다. G8은 실제 receipt-bound image rollback 2회(`nas-g8-723eeef2…`/`nas-g8-2738846c…`)에 더해 source HEAD `61a41d1f…6af`·tree `87dec55d…3b77`·archive `4d15d055…119d4d`의 candidate 112/112와 실제 NAS 평문 origin 112/112를 통과했다. 과거 `6030a677…c611`의 UUID 24건 실패와 후속 SHA 결함 rollback은 이력으로 보존하며 현재 완료 증거로 재사용하지 않는다. G7 Multimodal Alliance는 내부 구현 DONE과 외부 proof GATE를 분리한다. detached-clean public `a73bcd24…`·OpenAPI 126·`local_whisper`/`melotts` ready·authenticated WSS 무마이크 rehearsal까지 완료했고, 명시 동의 물리 마이크 3,120초·독립 라벨 voice-gain benchmark·동시 topology high-water를 추적한다. 외부 Deepgram/OpenAI adapter는 fallback으로 보존한다. G0~G8과
> AOS-001~012는 `docs/TODO.md` I절에서 전건 추적하고, 상태는 SSOT 대시보드의 9개 계획 카드가 소유한다.
> 이 얇은 백로그에는 그중 외부·환경 증거가 필요한 항목만 기존 B2/B4/Phase 3 게이트와 합쳐 유지한다.
>
> **2026-08-31 재검증(목표 마무리 기록)** — 아래 열린 항목 전건을 SSOT 대시보드(2026-08-29/30)·최신 핸드오프(`ops/handoff-goal-production-2026-08-29.md` §0)·실제 환경과 교차 재확인했다. 실증 게이트가 남아 있어 **가짜 증거로 DONE 체크하지 않는다**(운영 원칙).
> **운영 인프라 정정(중요):** 정식(production)은 `infra/docker-compose.nas.yml`**NAS `vignette-prod` 스택**(`ssot-host: nas`·`runtime-class: production`)으로 분리 운영 중이다. 2026-08-31 실측으로 public 도메인 `https://api-vignette.chanpaca.net/health`가 200·`status=ok`·`db=true`·`engine=true`·`environment=prod`·`engine_mode=openai`로 응답하는 반면, **이 Windows PC에는 cloudflared 프로세스가 없고 로컬 API `8001` listener도 없다** — 즉 public 도메인은 이 PC를 지나지 않는다. 이 PC는 개발 워크스테이션(과거 자택 public 호스트였던 경로의 잔여 상태: Docker `vignette-dev-db:55432` Up·로컬 엔진 `9099` LISTENING·`VignettePublicRuntime`/`Watchdog` 2개 Scheduled Task Disabled)이다. 따라서 아래 항목 중 이 PC의 `8001`/task/재부팅 관련 게이트(B2)는 **정식(NAS)과 분리된 이 PC/개발 런타임 기준**으로 읽어야 하며, public 도메인 장애를 뜻하지 않는다. push·Pages 배포·public runtime/task 변경은 별도 승인 전 미실행.
> 실제 닫힘 판정은 이 PC 기준이 아니라 **정식 운영 주체(NAS `vignette-prod` ingress·DB·도메인) 기준**으로 해야 하며, 이 PC에서 NAS ingress를 직접 실측할 수 없어 별도 확인이 필요하다. 2026-08-31 추가로 **public 도메인 읽기 전용 실브라우저 폐루프**(Playwright chromium: 로그인 화면 1 passed·데스크톱/모바일 200·Google OAuth redirect_uri 확인·dev-login 차단·`/auth/me` 401 외 에러 0)를 실증했다(증거 <code>ops/evidence/public-domain-readonly-browser-2026-08-31.json</code>) — 이는 public 도메인 정상 서빙의 추가 증거이며, 실제 Google 계정 로그인→callback→관리자/P20 실회기·턴 생성(production DB mutation)은 소유자 승인·계정 세션이 필요해 여전히 열린 게이트다. `public API/DB·아바타 정식 승격`, `G8 실DB/public 사람 게이트`, `음성 캐스케이드 live`, `DB 백업 운영화`, `vnet.18ka.net live`, `claude_cli↔Anthropic API live`, `Compose infra/.env`, `B1 티켓 자동 분류`, `C-001`, `한신대 거버넌스`, `L1 등재`, `Phase 3`은 전부 열린 게이트로 유지한다. 참고: `공개 DB 계정·회기 복구 안정화``stable-source 재부팅 후 watchdog smoke`는 사실상 **같은 잔여 게이트(실제 Windows 재부팅 후 자동복구 smoke)**를 추적 중이다 — 재부팅 smoke가 닫히면 두 항목과 REQ-008이 함께 닫힌다.
---
@ -197,6 +204,39 @@
## Phase 3 파일럿 게이트 (실참여자 필요)
---
## 배포 파이프라인 재정립 + A 경로 (NAS prod 실배포 → E2E 검증) — 2026-09-01 재부팅 후 실행
> **소유자 지시(2026-08-31)**: 배포는 Forgejo(git.chanpaca.net) 중심, NAS Production 대상, github은 private 백업/미러,
> 이 PC는 개발 전용, Cloudflare는 서빙용만·tunnel 제거. **A 경로로 진행**: 실제 prod 배포 준비 → 실배포 → 로그인·작동 E2E 확인까지.
> 2026-09-01 재부팅 후 아래를 순서대로 진행. 지침: `docs/ops/deployment-pipeline.md` · 핸드오프 `docs/ops/handoff-goal-production-2026-08-29.md`.
### 재부팅 직후 (truth 재확인)
- [ ] OS/셸/경로/도구 확정(AGENTS.md §0), git HEAD·worktree·status 확인 (재부팅 전 스냅샷: HEAD `be08c0b5`, Forgejo master == 로컬, 미커밋 98/untracked 27)
- [ ] live public API health·NAS prod(`vignette-prod`, `yunchan-nas`)·Forgejo(토큰) read-only 재확인 — `scripts/vignette-pipeline.py --check`
- [ ] NAS prod 배포 기계장치 확정: 현재 preview용 SSH target(`100.116.83.60:8088`)을 prod(192.168.0.38)용 경로로 분리/고정
### A-1. 배포 후보 GREEN 정리
- [ ] 이 PC 98미커밋/27untracked를 검토해 **기능 코드 diff만 좁게 stage**(dirty tree 전체 commit 금지 — 핸드오프 §11)
- [ ] `apps/web`: `npm run typecheck`·`npm run build` / `apps/api`: 관련 test suite / SSOT checker PASS
- [ ] Forgejo master로 배포 후보(hunk) 커밋을 좁게 push, 후보 SHA·tree 고정(레포: `yunchan/vignette`)
### A-2. NAS prod 실배포 (승인·백업 후)
- [ ] NAS prod 이미지 빌드·업로드→compose 교체 절차를 **prod 전용으로 설계**(영구 볼륨 `pgdata`/`apiuploads` 보존, 다운타임 허용 창, backup/rollback)
- [ ] 소유자 실배포 승인 문구 제시 → 승인 후 NAS prod `docker-compose.nas.yml` 기준 이미지 재빌드·교체
- [ ] 배포 후 readiness: public API `status=ok·db/engine true`, NAS 5개 컨테이너 healthy, OpenAPI/auth 계약 확인
### A-3. 로그인·작동 E2E 검증 (실브라우저)
- [ ] public 도메인 로그인 화면 렌더 + Google OAuth redirect_uri 검증 (`auth.spec.ts` public) — 읽기 전용
- [ ] 실제 Google 계정 로그인→callback→관리자/P20 실회기·턴 생성(production DB mutation) — 소유자 승인 + 계정 세션(storageState) 필요
- [ ] 배포된 WEB 200·새 asset hash·MIME, DB/이미지 fallback 확인 → **로그인·정상 작동까지 전건 GREEN일 때만 완료**
### 잔여(재부팅 외) — 그대로 유지
- [ ] 실배포 완료 전까지는 아래가 열린 게이트 유지: `public API/DB·아바타 정식 승격`, `G8 실DB/public 사람 게이트`,
`음성 캐스케이드 live`, `DB 백업 운영화`, `vnet.18ka.net live`, `claude_cli↔Anthropic API live`, `Compose infra/.env`,
`B1 티켓 자동 분류`, `C-001`, `한신대 거버넌스`, `L1 등재`, `Phase 3`, 재부팅 후 watchdog smoke/REQ-008
- [ ] **20명 교육용 파일럿 운영 / 효과성·KPI 측정(SUS·자기효능감·κ/ICC·환각률) / 재귀학습 데이터셋 approved 산출 /
개인정보·동의 감사.** 문서·checker·dry-run exporter는 준비됨(`docs/phase3/*`, `scripts/check-phase3-artifacts.py`,
`scripts/export-recursive-dataset.py`, `scripts/export-phase3-kpi.py`). 공식 문항 확정, 통계 검정, 실험/통제군 배정,

View file

@ -0,0 +1,62 @@
{
"$schema": "evidence/v1",
"id": "public-domain-readonly-browser-2026-08-31",
"title": "public 도메인 읽기 전용 브라우저 폐루프 실측 (production, 이 PC와 분리 확인)",
"date": "2026-08-31",
"operator": "agent (read-only, production DB 미접촉)",
"scope": "읽기 전용. 사용자 계정 로그인/회기·턴 생성(production DB mutation) 없음. 배포·설정 변경 없음.",
"targets": {
"public_web": "https://vignette.chanpaca.net/login",
"public_api": "https://api-vignette.chanpaca.net"
},
"method": "Playwright 1.61.1 chromium (desktop 1440x900 / mobile 390x844) + curl/HTTP 실측",
"evidence": {
"playingwright_public_login_spec": {
"spec": "e2e/auth.spec.ts :: keeps the public login screen on real Google OAuth only",
"result": "1 passed (9.1s)",
"verified": [
"GET api-vignette.chanpaca.net/auth/config -> google_oauth_configured=true, dev_login_enabled=false, allowed_email_domains=[]",
"vignette.chanpaca.net/login -> Google 로그인 버튼(.lg-obtn) 1개, dev 로그인 요소 0, 'Google OAuth is not configured' 없음",
"Google 버튼 클릭 -> accounts.google.com 이동, client_id 포함",
"redirect_uri=https%3A%2F%2Fapi-vignette.chanpaca.net%2Fauth%2Fcallback"
]
},
"production_render_probe": {
"desktop_mobile_http": [200, 200],
"has_google_button": true,
"title": "Vignette · 상담 시뮬레이션",
"heading": "실제 계정으로 들어가고, 실제 회기만 남깁니다.",
"has_logo": true,
"has_error_text_fallback": false,
"console_page_errors_beyond_expected_auth": [],
"only_401": {
"request": "GET https://api-vignette.chanpaca.net/auth/me",
"reason": "비로그인 방문자 조회로 반환되는 정상 인증 경계 401 (로그인 페이지가 미인증 상태를 판별해 Google 버튼 표시)"
}
},
"public_api_health_x3": [
"200 status=ok environment=prod db=true engine=true engine_mode=openai"
],
"serving_identity": {
"dns": ["104.21.33.204", "172.67.149.131", "2606:4700:3030::6815:21cc", "2606:4700:3030::ac43:9583"],
"header_server": "cloudflare",
"cf_ray": "a33aa06b4c5ee9e1-LAX",
"conclusion": "이 PC가 아닌 별도 호스트(Cloudflare 뒤)가 서빙. 이 PC에는 cloudflared 프로세스 없음(확인) -> 이 PC와 정식 분리 운영"
},
"this_pc_state": {
"cloudflared_process": "absent",
"local_api_8001_listener": "absent",
"scheduledTasks": { "VignettePublicRuntime": "Disabled", "VignettePublicRuntimeWatchdog": "Disabled" },
"containers": "vignette-dev-db (up, 55432)",
"note": "개발 워크스테이션 (과거 자택 public 호스트 경로의 잔여 상태). 이 PC 로컬 상태는 public 도메인 장애가 아님."
}
},
"conclusion": {
"closed_readonly": "public 도메인(production)은 별도 호스트에서 정상 서빙 중. 실브라우저 로그인 화면·Google OAuth redirect_uri·개발 로그인 차단이 읽기 전용으로 실증됨. 이 PC와 정식 분리 운영이 실측으로 확인됨.",
"open_gate": "실제 Google 계정 로그인 -> 인증 callback -> 관리자/P20 실제 회기·턴 생성(production DB mutation)은 사용자(소유자) 승인과 계정 세션(storageState)이 필요해 여전히 열린 게이트. production DB mutation 없이는 read-only로는 증명 불가."
},
"screen_captures": [
"apps/web/node_modules/.tmp/public-login-desktop.png",
"apps/web/node_modules/.tmp/public-login-mobile.png"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 709 KiB

After

Width:  |  Height:  |  Size: 403 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 KiB

After

Width:  |  Height:  |  Size: 655 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 760 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 912 KiB

After

Width:  |  Height:  |  Size: 483 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 873 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 KiB

After

Width:  |  Height:  |  Size: 362 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 810 KiB

After

Width:  |  Height:  |  Size: 696 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 KiB

After

Width:  |  Height:  |  Size: 574 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 944 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 KiB

After

Width:  |  Height:  |  Size: 286 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 KiB

After

Width:  |  Height:  |  Size: 396 KiB

Before After
Before After