상태판·가이드와 검증 증거를 동기화
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-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-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-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 비종속 상시 호스트 이전도 별도 운영 게이트다. | | 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…`)는 유지되지만, 2026-08-29 read-only 상태(<b>이 PC 로컬 기준</b>): 정적 public Web과 Pages `0c60261e…`(source `5bf89ff…`)는 유지됐지만,
local API 8001은 연결 거부, public API는 530이며 Docker daemon이 없어 DB/API는 **OUTAGE**다. task는 old 이 PC 로컬 API 8001은 연결 거부였고 이 PC의 Docker daemon 부재로 이 PC 기준 DB/API는 복구 불가였다. task는 old
detached `dba9b75a…` runtime을 계속 가리킨다. 과거 `status=ok·db=true·engine=true`와 watchdog result 0은 이전 성공 detached `dba9b75a…` runtime을 계속 가리켰다. <b>2026-08-31 재실측(정식 분리 확인):</b> `https://api-vignette.chanpaca.net/health`
증거이지 현재 GREEN이 아니다. avatar fail-closed 코드와 upload receipt는 아직 후보이고 배포하지 않았다. 별도 호스트(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="brand"><span class="mark" aria-hidden="true"></span><span>Vignette 커맨드센터<small>팀장 리뷰 · dev status</small></span></div>
<div class="head-right"> <div class="head-right">
<a class="owner-pill" href="#owner"><span class="d" aria-hidden="true"></span>내 차례 <b id="pill-owner">0</b></a> <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> </div>
</header> </header>
@ -300,10 +300,13 @@
<section class="pulse"> <section class="pulse">
<div class="pulse-main"> <div class="pulse-main">
<span class="eyebrow">project pulse</span> <span class="eyebrow">project pulse</span>
<h1>지금 위치: <em>공개 정적 Web은 유지</em>, <em>Public API·DB는 OUTAGE</em>. 이미지 fail-closed 후보·E2E와 C-001·REQ-008 사람 게이트가 남았다.</h1> <h1>지금 위치: <em>public 도메인은 별도 호스트(Cloudflare)에서 정상 서빙 중</em>(이 PC와 분리 운영), NAS preview는 degraded. 이미지 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> <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"> <details class="pulse-details">
<summary>운영 복구·watchdog·배포 상세 보기</summary> <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 최신 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 아바타 무결성:</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> <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">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 good">Alliance G1 baseline 1.2 <b>9/9</b> · 내부 DONE</span>
<span class="mchip">Tailnet <b>이전 확인 OK</b></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">이전 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 good">Direct-runtime preflight <b>APP ROLE OK</b></span>
<span class="mchip crit">Avatar decode <b>3 valid · 90 invalid</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">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">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">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 class="dg-principles" aria-label="디자인 생성 가드레일">
<div><b>래스터만 사용</b><span>이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.</span></div> <div><b>래스터만 사용</b><span>이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.</span></div>
<div><b>기능 우선</b><span>메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.</span></div> <div><b>기능 우선</b><span>메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.</span></div>
@ -1119,7 +1124,7 @@
<tbody> <tbody>
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr> <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>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>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>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> <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>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>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 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 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-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 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>배포된 동적 런타임/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 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 실제 회기 폐루프는 유효한 과거 증거다. 2026-08-29에는 public API 530으로 신규 callback·관리자·G8 실DB E2E를 재검증할 수 없으므로 현재 운영 GREEN으로 재사용하지 않는다. public Web은 정적 로그인 화면만 200이다.</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>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/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> <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). 회기 라이프사이클 메모리(4계층 매핑: ① working / ② episodic / ③ summary / ④ semantic).
- 회기 시작: `(persona_id, learner_id)` 안정 `case_profile`을 확보한 뒤 - 회기 시작: `continue`는 선택한 owned `case_id`(구클라이언트는 가장 최근 사례)를 같은
`build_recall_context(...) -> RecallContext`를 조립한다. 동기 seed는 생성 트랜잭션 안에서 잠그고 `build_recall_context(...) -> RecallContext`를 조립한다.
`case_profile.case_digest` + 직전 `session_summary` + client-visible `pinned_fact` `fresh`는 활성 회기 검사를 먼저 통과한 뒤 빈 `case_profile`을 새로 만들어 항상 1회기·빈
기반이고, episodic 단편/KB 단서는 백그라운드 warm cache로 붙는다. recall로 시작한다. 동기 seed는 이어지는 사례의 `case_profile.case_digest` + 직전
`session_summary` + client-visible `pinned_fact` 기반이고, episodic 단편/KB 단서는
백그라운드 warm cache로 붙는다.
- 회기 종료: 마스킹된 client-visible 축어록으로 fallback `session_summary.digest`를 만들고, - 회기 종료: 마스킹된 client-visible 축어록으로 fallback `session_summary.digest`를 만들고,
같은 트랜잭션에서 `case_profile.case_digest`, `rapport_trajectory`, `alliance_level` 같은 트랜잭션에서 `case_profile.case_digest`, `rapport_trajectory`, `alliance_level`
갱신한다. 동시에 마스킹된 client-visible 발화에서 `[NAME]`/`[ORG]` identity와 명시적 상담 약속만 갱신한다. 동시에 마스킹된 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 → - `POST /sessions``get_catalog_persona(code)`로 승인 카드 조회 → `start_mode=fresh|continue`
case digest/직전 summary/pinned fact seed recall → `state_machine.init_state(...)` 선택 `case_id`를 영속 생성 트랜잭션에 전달 → 사례별 digest/직전 summary/pinned fact seed recall →
`session_persistence.create_session(...)`. 생성 시 승인 페르소나의 `persona_id``persona_version` `state_machine.init_state(...)``session_persistence.create_session(...)`. `fresh`는 기존 사례의
기억을 읽지 않는 별도 case를 만들고, `continue`는 소유한 해당 case만 이어간다. 생성 시 승인 페르소나의
`persona_id``persona_version`
세션에 고정하고 시작/상세 응답에도 두 값을 반환한다. 이후 더 높은 버전이 승인돼도 기존 회기는 고정된 세션에 고정하고 시작/상세 응답에도 두 값을 반환한다. 이후 더 높은 버전이 승인돼도 기존 회기는 고정된
역사 버전을 해석한다. DB 영속 생성 실패는 안전하지 않은 성공으로 흡수하지 않고 안정적인 역사 버전을 해석한다. DB 영속 생성 실패는 안전하지 않은 성공으로 흡수하지 않고 안정적인
`503 session_persistence_unavailable`로 반환한다. 프론트 세션 시작 전 화면은 `persona.theory_target` `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회 이상에서 제외한다. `growth.training_exposure`는 종료 회기만 집계하고 4회 미만이면 `insufficient`, 4회 이상에서
최다 페르소나 비중이 0.75 이상이면 `훈련 집중 주의`, 그 밖에는 `balanced`로 표시한다. 투명한 노출 비중이지 최다 페르소나 비중이 0.75 이상이면 `훈련 집중 주의`, 그 밖에는 `balanced`로 표시한다. 투명한 노출 비중이지
공정성·임상 진단이 아니다. 성취는 공식 등급/수료가 아니라 실제 연습 milestone만 표시한다. 공정성·임상 진단이 아니다. 성취는 공식 등급/수료가 아니라 실제 연습 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 산출물을 - `GET /sessions/{id}/review` — 저장된 축어록 + 평가 AI 산출물을
`session_read_model.build_session_review(...)`가 학습자-안전 리뷰로 구성한다. 리뷰 조회 시 발화별 fast-loop 평가는 `session_read_model.build_session_review(...)`가 학습자-안전 리뷰로 구성한다. 리뷰 조회 시 발화별 fast-loop 평가는
`app.feedback_scores`/라벨 조인 테이블에서 `TurnRecord.evaluation` 형태로 hydrate한다. `app.feedback_scores`/라벨 조인 테이블에서 `TurnRecord.evaluation` 형태로 hydrate한다.
@ -775,7 +784,7 @@ React 19 + Vite. 라우팅은 `apps/web/src/App.tsx`(react-router-dom).
## 5. 데이터베이스 스키마 개요 ## 5. 데이터베이스 스키마 개요
DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에 번호순으로 적용되며, DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에 번호순으로 적용되며,
개선관리 계약까지 필요한 현행 끝점은 `17_improvement_workbook_contracts.sql`이다. 개선관리·회기 순차성 계약까지 필요한 현행 끝점은 `21_single_active_session.sql`이다.
스키마는 **4분할**: `app` / `kb` / `audit` / `ds`. 스키마는 **4분할**: `app` / `kb` / `audit` / `ds`.
### 5.1 app 스키마 (`infra/db/init/02_schema.sql`) ### 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). `app.counselor_profile`(상담사 AI).
- **라벨 코드테이블**(taxonomy 3축) — `stage_def`, `technique_label_def`, `client_state_def`, `scale_def`. - **라벨 코드테이블**(taxonomy 3축) — `stage_def`, `technique_label_def`, `client_state_def`, `scale_def`.
- **세션** `app.sessions` — case_id/persona_id/persona_version 핀, stage_path. `case_id` - **세션** `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` 단위의 학습자 보기 상태. - **회기 보관 상태** `app.session_archive_state``session_id`/`learner_id` 단위의 학습자 보기 상태.
`archived_at`/`updated_at`만 저장하고 restore 시 row를 삭제한다. RLS는 학습자 본인의 보관/복원과 `archived_at`/`updated_at`만 저장하고 restore 시 row를 삭제한다. RLS는 학습자 본인의 보관/복원과
teacher/admin 조회만 허용하며, 원 세션·발화·리뷰·공유 링크는 보존한다. teacher/admin 조회만 허용하며, 원 세션·발화·리뷰·공유 링크는 보존한다.
@ -921,7 +933,7 @@ DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에
- ① WORKING `app.session_state` — 상태머신 수치 체크포인트(매 턴 UPSERT, openness/ideation CHECK 제약). - ① WORKING `app.session_state` — 상태머신 수치 체크포인트(매 턴 UPSERT, openness/ideation CHECK 제약).
- ② EPISODIC 임베딩 `app.turn_embedding` — BGE-M3 dense `vector(1024)` + sparse, HNSW 인덱스. - ② EPISODIC 임베딩 `app.turn_embedding` — BGE-M3 dense `vector(1024)` + sparse, HNSW 인덱스.
- ③ SUMMARY `app.session_summary` — (A) end_state 무손실 carry-over + (B) digest narrative. - ③ 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 (+`pinned_fact_history` append-only). 현재 자동 쓰기는 learner-owned case의 마스킹 identity/agreement
fact만 허용하고, 삽입/값 변경 history와 명시적 상담 약속 철회 contradiction까지 남긴다. fact만 허용하고, 삽입/값 변경 history와 명시적 상담 약속 철회 contradiction까지 남긴다.
관계·임상 fact 승격과 광범위 자동 모순 판정은 후속이다. 관계·임상 fact 승격과 광범위 자동 모순 판정은 후속이다.
@ -934,6 +946,26 @@ DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에
기존 DB는 owner가 단일 트랜잭션으로 실행한다. 애플리케이션 역할 startup은 readiness만 확인하며 runtime 기존 DB는 owner가 단일 트랜잭션으로 실행한다. 애플리케이션 역할 startup은 readiness만 확인하며 runtime
DDL로 빠진 계약을 보충하지 않는다. 누락되면 migration 17 적용을 요구하며 fail-closed한다. 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`) ### 5.2 audit 스키마 + app 평가/종단 (`infra/db/init/04_audit_eval_rls.sql`)
- 평가: `app.feedback_scores`(발화별 점수·rationale, `visible_to='{evaluator}'`, loop fast/deep), - 평가: `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과 프로토콜 레지스트리 ### 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으로 한 번 적용해야 한다. 애플리케이션 역할이 startup에서 테이블을 만들지 않으므로 owner DSN으로 한 번 적용해야 한다.
```powershell ```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 색인이 한 트랜잭션에서 성공해야 끝난다. 활성화는 라이선스·`external_llm_ok` 검증과 evaluator-only RAG 색인이 한 트랜잭션에서 성공해야 끝난다.
라이선스 C/D는 외부 LLM 사용을 허용할 수 없다. 라이선스 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. 웹(프런트엔드) 실행 ## 4. 웹(프런트엔드) 실행

View file

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

View file

@ -20,8 +20,11 @@
> 승인 10필드와 서명 증거가 없어 B4 외부 GATE로 유지하며, `review_complete=true` 전에는 완료로 닫지 않는다. > 승인 10필드와 서명 증거가 없어 B4 외부 GATE로 유지하며, `review_complete=true` 전에는 완료로 닫지 않는다.
> 현재 개선관리 집계는 **완료 9·검토 2·총 11**이다. C-001 외부 입력 46칸은 조작하지 않는다. > 현재 개선관리 집계는 **완료 9·검토 2·총 11**이다. C-001 외부 입력 46칸은 조작하지 않는다.
> >
> **2026-08-29 최신 운영 상태** — 정적 public Web은 200이지만 local API 8001은 연결 거부, public API는 530이고 > **2026-08-29 최신 운영 상태(이 PC 로컬 기준)·2026-08-31 재실측 정정** — 당시 이 PC 로컬 API 8001은 연결 거부였고 이 PC의
> Docker daemon 부재로 DB/API는 OUTAGE다. 배포된 동적 runtime/task 기준선은 old `dba9b75a…`, Pages는 > 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 > `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을 분리하며, > 정상 3/실패 90, 현 DB 참조 정상 2/실패 6/missing 0이다. fail-closed 후보와 deployed baseline을 분리하며,
> 전체 회귀·아바타 실브라우저 fallback·G8 실DB/public 증거·사용자 배포 승인 전에는 운영 전체를 GREEN으로 올리지 않는다. > 전체 회귀·아바타 실브라우저 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과 > 현재 소스·실행 증거 재감사에서는 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개 계획 카드가 소유한다. > AOS-001~012는 `docs/TODO.md` I절에서 전건 추적하고, 상태는 SSOT 대시보드의 9개 계획 카드가 소유한다.
> 이 얇은 백로그에는 그중 외부·환경 증거가 필요한 항목만 기존 B2/B4/Phase 3 게이트와 합쳐 유지한다. > 이 얇은 백로그에는 그중 외부·환경 증거가 필요한 항목만 기존 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 파일럿 게이트 (실참여자 필요) ## 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 산출 / - [ ] **20명 교육용 파일럿 운영 / 효과성·KPI 측정(SUS·자기효능감·κ/ICC·환각률) / 재귀학습 데이터셋 approved 산출 /
개인정보·동의 감사.** 문서·checker·dry-run exporter는 준비됨(`docs/phase3/*`, `scripts/check-phase3-artifacts.py`, 개인정보·동의 감사.** 문서·checker·dry-run exporter는 준비됨(`docs/phase3/*`, `scripts/check-phase3-artifacts.py`,
`scripts/export-recursive-dataset.py`, `scripts/export-phase3-kpi.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