diff --git a/docs/TODO.md b/docs/TODO.md index 92b0187..797b527 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -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 상태(이 PC 로컬 기준): 정적 public Web과 Pages `0c60261e…`(source `5bf89ff…`)는 유지됐지만, +이 PC 로컬 API 8001은 연결 거부였고 이 PC의 Docker daemon 부재로 이 PC 기준 DB/API는 복구 불가였다. task는 old +detached `dba9b75a…` runtime을 계속 가리켰다. 2026-08-31 재실측(정식 분리 확인): `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는 사용하지 않는다. --- diff --git a/docs/dev_dashboard.html b/docs/dev_dashboard.html index 4b1a60b..3bb3156 100644 --- a/docs/dev_dashboard.html +++ b/docs/dev_dashboard.html @@ -291,7 +291,7 @@
현재: 배포된 API/task 기준선은 dba9b75a…, Pages는 0c60261e…(source 5bf89ff…)지만 Docker daemon 부재로 local 8001은 연결 거부, public API는 530이다. 아바타 decode fail-closed 변경은 후보 코드일 뿐 미배포라 운영 GREEN이 아니다. G8 차단·보류·반려·승인의 격리 local 내장 브라우저 proof는 GREEN이다. 다음: 전체 통합 GREEN과 G8 실DB/public proof를 확인한 뒤 push/Pages/runtime/task 전환 승인을 받고, 실제 재부팅은 별도 승인으로 실행한다.
2026-08-31 배포 파이프라인 재정립(소유자 지시): git 관리·배포는 git.chanpaca.net(Forgejo 11.0.16) 중심으로 이관 완료 — yunchan/vignette 레포 생성, master=be08c0b5 push, 소스 SSOT 지정. github.com(origin)은 private 백업/미러로 유지(제거 아님). 배포 대상은 NAS Production(docker-compose.nas.yml, vignette-prod)이며 이 PC는 단순 개발용. Cloudflare는 서비스 공개 서빙용만 유지, cloudflare tunnel은 제거 대상. 관리 도구 scripts/vignette-pipeline.py --check로 기준선 무결성 점검(ALL OK): 문서 deployment-pipeline.md.
현재: 2026-08-30 NAS vignette-prod의 DB·API·engine은 healthy이고, restart loop이던 web/proxy는 필요한 최소 capability만 추가해 재생성했다. Caddy 내부 경유 web 및 /api/health는 200이지만, compose가 bind하는 Tailnet 주소가 NAS에 없어 외부 private/public GREEN은 주장하지 않는다. 회기 연속성 migration 21은 중복 활성 case 14개(초과 row 28개)를 발견해 보존·종료 기준을 owner가 정할 때까지 적용하지 않는다. 아바타 decode fail-closed 변경은 후보 코드일 뿐 미배포다. 다음: 중복 회기 정리 기준·Tailnet 복구·production 전용 clean candidate를 확정한 뒤 DB 적용, 실제 브라우저 E2E, 외부 ingress를 순서대로 검증한다.
2026-08-31 사례 연속성 후보: source candidate에는 같은 NPC의 완전히 새로 시작(빈 새 case)과 이어서 진행(선택 case의 압축 기억)을 분리했고, 사례별 누적 회기·상담자 가시 턴·누적 시간을 시작 전 표시하며 기억은 lazy foldout으로 숨겼다. migration 22는 legacy case unique만 해제하고 migration 21의 단일 활성 회기 제약을 유지한다. 이는 source/test 후보 상태이며 NAS DB 적용·배포·실제 production 브라우저 GREEN은 아직 하지 않았다.
2026-08-30 NAS production preflight·ingress 복구: vignette-prod-db-1·vignette-prod-api-1·vignette-prod-engine-1은 healthy였다. vignette-prod-web-1은 Nginx startup에 필요한 최소 capability, vignette-prod-private-proxy-1는 Caddy bind에 필요한 최소 capability가 빠져 restart loop이었고, 두 서비스만 재생성해 restart 0의 running 상태와 Caddy→web/API health 200을 확인했다. DB·upload volume·API·engine·tunnel은 건드리지 않았다. 다만 configured Tailnet bind 100.116.83.60는 NAS 인터페이스에 없어 host port가 열리지 않는다. LAN/0.0.0.0 bind로 넓히지 않았으며, Tailnet 자체 복구 또는 owner가 정한 보안 경계를 먼저 적용해야 한다.
2026-08-29 최신 read-only 상태: VignettePublicRuntime과 watchdog은 여전히 old dba9b75a… root를 가리키며, Docker Desktop Linux engine pipe가 없어 DB/API 복구에 실패한다. 정적 로그인 Web 200은 동적 API 정상 증거가 아니다. 운영 변경 승인 전에는 Docker 시작·API/tunnel 재시작·task 재등록·DB/avatar URL 수정·stable upload root cutover를 하지 않는다.
2026-08-29 아바타 무결성: 허용 source union은 93 objects · 52,973 bytes · SHA-256 9d703126…e6aa다. 실제 decode는 정상 3·실패 90, 현 DB 참조는 정상 2·실패 6·missing 0이다. 후보는 손상 bytes와 참조를 private forensic으로 보존하되 public serve는 404/fallback으로 막고, decode-valid 객체만 immutable public cache에 싣는다. 이 후보는 전체 API/Web/E2E·내장 브라우저·운영 배포 검증 전까지 GREEN이 아니다.
2026-08-29 재부팅 복구 hardening의 이전 성공 증거: Windows PowerShell 5.1의 timed WaitForExit()가 process handle 선확보 없이는 성공한 web build의 ExitCode를 null로 남겨 실패로 오판하는 경계를 수정했다. 실제 PS5.1 계약 32 passed, detached-clean 44b7835c…·tree 06133249…에 로그온·5분 watchdog을 같은 핀으로 재등록했다. 당시 watchdog 3회차 복구로 5174가 자동 기동해 HTTP 200을 반환했고 후속 실행은 LastTaskResult=0·failcount 0, local/public health status=ok·db=true·engine=true였다. 기존 ExecutionTimeLimit=PT1H·MultipleInstances=IgnoreNew·exclusive mutex·bounded wait·process-tree 종료·hard-down 즉시복구 계약도 유지한다. 이것은 로그인 뒤 과거 복구 증거이며 현재 장애나 실제 Windows 재부팅 smoke를 대신하지 않는다.
220차 개선관리 완료본 현행 증거 동기화(2026-08-29): Vignette_개선관리_완료.xlsx의 상태·수식·서식은 유지하고 개발·기능 이슈!M5:M12와 임상·교육 이슈!M6:M7의 오래된 API/Pages 배포 근거만 현재 dba9b75a…·tree 14cd4607…·Pages ef48c0ae…로 갱신했다. artifact-tool import→values-only edit→export→reimport 뒤 6개 전 시트와 수정 집중 4개 렌더를 직접 확인했고 수식 오류 0, 완료 8·검토 3·총 11, 임상 checker pending_external_review·review_complete=false를 유지한다. 새 완료본 SHA는 aab6ef52…e26a다. 기존 C-001 사전검증 원장은 당시 14개 해시의 역사 증거로 보존하고, 현행 원장에서 기술 산출물 13/13 불변과 새 워크북 해시를 결합해 현재 패키지 14/14 연속성을 다시 확인했다. 실제 Gmail callback·Windows 재부팅 smoke·외부 임상 승인 없이 상태를 완료로 올리지 않았다. 기계 판독 증거는 docs/ops/evidence/workbook-runtime-sync-2026-08-29.json이다.
221차 교수자 콘솔 표면 간격 교정·공개 승격(2026-08-29): /teach의 검토 큐와 교수자 요약이 독립 Surface인데 부모 .pf-signal-strip의 gap:0 때문에 테두리·그림자가 맞붙던 원인을 --sp-3=12px로 교정했다. 수정 전 0px를 다시 주입해 새 회귀 검사가 RED가 되는 것을 확인하고 복원 뒤 focused 1 passed, 390~1440 7폭 교수자 gate 1 passed, 전체 layout visual 15 passed, session 무회귀 8 passed, typecheck·design SSOT·lint·build를 통과했다. tracked-clean 5bf89ff4…·tree 29f76aeb…에서 599파일 build 2회 byte-identical, 이전 자산 172개 보존 후 Pages production 0c60261e-cb37-482d-ba42-d91586194c48로 승격했다. 실서비스 1425×1272 DOM은 computed/실측 간격 12px·overflow 0·console warning/error 0이고 custom/preview의 Professor-BfR874KA.css SHA 63dbe2c7…7fff가 clean build와 일치한다. 증거는 docs/ops/evidence/professor-summary-gap-deploy-2026-08-29.json이다.
222차 REQ-001 완료·개선관리 재생성(2026-08-29): 기존 실서비스 인증 세션은 yunchan8804@gmail.com으로 설정·관리자 콘솔에 접근하고 온보딩으로 되돌아가지 않으며, 학습 이력 20건·리뷰 필요 6건을 표시했다. 소유자가 복구 데이터 가시성을 수락했고 최신 Pages 0c60261e…의 단일 Google CTA 1개·구 선택 버튼 0·dev login 0·무제한 도메인·공개 Playwright 2 passed를 함께 확인해 REQ-001을 완료로 승격했다. 개선관리 완료본은 artifact-tool 구조 추출로 6시트·11개 고유 요구·38수식·오류 0·완료 9/검토 2를 확인했다. C-001은 패키지 8/8·기술 사례 6/6·canonical checker 정상의 pending-valid이며 외부 입력 46칸은 공란으로 보존했다. 완료본 SHA는 c832547f…d8bd, sidecar SHA는 8c9618f0…a610이고 REQ-008 실제 Windows 재부팅 smoke와 C-001 외부 임상 승인만 검토로 남는다.
223차 REQ-007 회기 연속성 보강(2026-08-30, source-only): 종료 기록의 모호한 다시 연습 행동을 다음 회기 이어가기로 바꾸고, 같은 learner-persona case에 진행 중 회기가 있으면 API가 409 active_session_exists와 기존 session id를 돌려 브라우저가 그 회기로 이동하게 했다. 종료·시작은 동일 case row를 먼저 잠가 S2가 S1의 summary/carry를 빠뜨리지 않으며, migration 21의 online partial unique index와 invalid/wrong-index 전·후 condition이 병렬 active row를 fail-closed로 막는다. 종료 영속 뒤 다음 회기는 평가 완료를 기다리지 않는다. 연속성 대상 API 87 passed(현 작업트리의 별도 stale-evaluation timeout 기대치 1건 제외), release agent 32 passed, Playwright history 14 passed·continuity guard 1 passed, typecheck·API type check·production build를 확인했다. 기존 DB migration 21은 owner의 duplicate/target-index read-only audit 및 명시 적용이 필요하며, 공개/NAS/DB에는 적용·배포하지 않았다.
224차 NAS production preflight·ingress 복구(2026-08-30): 운영 DB에는 같은 learner-persona의 중복 활성 case 14개(초과 active row 28개)가 있어 migration 21의 partial unique index를 적용하지 않았다. 과거 회기 중 turn이 있는 row를 자동 종료하거나 삭제하지 않고 owner의 보존·종료 기준을 기다린다. 운영에는 이미 transactional 20_public_bootstrap_ticket_events.sql이 있으므로 새 online index는 21_single_active_session.sql로 번호를 분리했다. 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가 별도로 필요하다.
npm run typechecknpm run check:design-ssot / npx playwright test e2e/auth-visual.spec.ts --project=chromium-single-run --reporter=line / npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --reporter=linenpm run e2e:parallel / npm run e2e:single-run / npm run e2e:listnpm run e2e:parallel / npm run e2e:single-run / npm run e2e:listruff check app / pytest -q app / pytest -q engine_gateway / npm run typecheck / npm run check:api-types / npm run check:design-ssot / npm run check:dead-code / npm run check:duplication / npm run build / npm audit --audit-level=high / full Playwrightops/refactor-governance-2026-07-15.md.npm run check:api-typessrc/lib/api.gen.ts stale checkpy -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 / scripts/check-measurement-ledger.sql / measurement·API contract checks / web typecheck / DB-backed session-persistence focused E2E 3종/auth/config + /auth/login?provider=googleallowed_email_domains=[], redirect URI https://api-vignette.chanpaca.net/auth/callback, dev-login disabled. 로그인 DOM은 모든 Google 계정을 명시하고 실제 Google 선택기에 yunchan@twentyoz.kr와 yunchan8804@gmail.com이 함께 노출됐다. 기존 Google 계정 선택→callback→/admin 성공을 확인했다. 신규 Gmail 선택은 Vignette 계정 생성이므로 사용자 행동시점 확인 뒤 별도 실증한다.GET /personas/personas는 401 not authenticated를 반환한다.auth.spec.ts --grep public loginstart/watch/boot-public-runtime*.ps1 · install-public-runtime-task.ps1 · register-boot-task.ps1dba9b75a… runtime을 계속 가리킨다. Docker daemon 부재로 runtime task는 실패했고 watchdog도 DB/API를 복구하지 못한다. 새 total-size/decode receipt 후보는 미배포이며, 후보 통합 GREEN·사용자 승인 전 task 재등록이나 runtime 재시작을 하지 않는다. 실제 Windows 재부팅 자동복구 smoke도 별도 운영 gate다.http://127.0.0.1:8001/health / https://api-vignette.chanpaca.net/healthstatus=ok·db=true·engine=true와 voice exact 값은 과거 성공 증거로만 보존한다.health + provenance + Scheduled Tasks + Pagesdba9b75a…·tree 14cd4607…, Cloudflare Pages production은 0c60261e-cb37-482d-ba42-d91586194c48(source 5bf89ff4…)다. 현재 API/DB는 장애이고 fail-closed avatar 후보와 새 upload receipt는 아직 이 기준선에 승격되지 않았다. 후보와 deployed baseline을 분리하며, 승인 전 Docker/API/tunnel/task/DB/upload-root mutation을 하지 않는다.https://vignette.chanpaca.net/login / https://api-vignette.chanpaca.net/health / https://alpaca-home.taile93291.ts.net/loginstart/watch/boot-public-runtime*.ps1 · install-public-runtime-task.ps1 · register-boot-task.ps1cloudflared 프로세스가 없고 VignettePublicRuntime/Watchdog 두 Scheduled Task는 Disabled이며 로컬 8001 listener도 없다 — 이 PC는 더 이상 public 호스트 역할을 하지 않는다. public 도메인은 별도 호스트에서 정상 서빙 중(health 200). 새 avatar fail-closed receipt 후보는 여전히 미배포이며, 사용자 승인 전 이 PC의 task 재등록·runtime 재시작을 하지 않는다. 실제 Windows 재부팅 자동복구 smoke는 별도 운영 gate다.http://127.0.0.1:8001/health / https://api-vignette.chanpaca.net/healthenvironment=prod·db=true·engine=true·engine_mode=openai로 정상 응답한다(Server: cloudflare, 이 PC가 아닌 별도 호스트가 서빙). 이 PC 로컬 127.0.0.1:8001은 listener 없음(이 PC가 public 호스트 역할을 안 하므로)이며, 이는 도메인 장애가 아니라 개발 워크스테이션과 정식 운영의 분리를 반영한다. NAS preview 8088은 별개로 degraded다. 2026-08-29 OUTAGE 기록은 그 시점의 이 PC 기준 스냅샷으로, 오늘 실측이 이를 대체한다.health + provenance + Scheduled Tasks + Pagesdba9b75a…·tree 14cd4607…, Pages production 0c60261e…(source 5bf89ff4…)다. 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을 하지 않는다.https://vignette.chanpaca.net/login / https://api-vignette.chanpaca.net/health / https://alpaca-home.taile93291.ts.net/loginhttps://api-vignette.chanpaca.net/auth/callback 확인, dev-login 차단, 예상된 /auth/me 401 외 콘솔/페이지 에러 0 — 읽기 전용 실증 완료(증거). 실제 Google 계정 로그인→callback→관리자·P20 실회기·턴 생성은 production DB mutation이라 소유자 승인·계정 세션(storageState)이 필요해 여전히 열린 게이트다.PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 auth.spec.tslearner.spec.ts + readiness.spec.ts desktop/mobileuc-learner-home-dashboard.spec.ts --grep "성장 지표 라포 헤더"ops/evidence/public-domain-readonly-browser-2026-08-31.json) — 이는 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`). 공식 문항 확정, 통계 검정, 실험/통제군 배정,
diff --git a/docs/ops/evidence/public-domain-readonly-browser-2026-08-31.json b/docs/ops/evidence/public-domain-readonly-browser-2026-08-31.json
new file mode 100644
index 0000000..e88fafb
--- /dev/null
+++ b/docs/ops/evidence/public-domain-readonly-browser-2026-08-31.json
@@ -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"
+ ]
+}
diff --git a/docs/ops/evidence/self-directed-loop-active-session-320x568-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-active-session-320x568-chromium-mobile-2026-08-07.png
index 0a3dd20..ca484b1 100644
Binary files a/docs/ops/evidence/self-directed-loop-active-session-320x568-chromium-mobile-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-active-session-320x568-chromium-mobile-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-active-session-chromium-desktop-2026-08-07.png b/docs/ops/evidence/self-directed-loop-active-session-chromium-desktop-2026-08-07.png
index b9827ff..fc984d6 100644
Binary files a/docs/ops/evidence/self-directed-loop-active-session-chromium-desktop-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-active-session-chromium-desktop-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-active-session-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-active-session-chromium-mobile-2026-08-07.png
index b6703de..03570cd 100644
Binary files a/docs/ops/evidence/self-directed-loop-active-session-chromium-mobile-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-active-session-chromium-mobile-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-home-320x568-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-home-320x568-chromium-mobile-2026-08-07.png
new file mode 100644
index 0000000..9994ebb
Binary files /dev/null and b/docs/ops/evidence/self-directed-loop-home-320x568-chromium-mobile-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-home-chromium-desktop-2026-08-07.png b/docs/ops/evidence/self-directed-loop-home-chromium-desktop-2026-08-07.png
index b748b06..318d971 100644
Binary files a/docs/ops/evidence/self-directed-loop-home-chromium-desktop-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-home-chromium-desktop-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-home-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-home-chromium-mobile-2026-08-07.png
index bded031..cb72ea5 100644
Binary files a/docs/ops/evidence/self-directed-loop-home-chromium-mobile-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-home-chromium-mobile-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-repractice-home-320x568-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-repractice-home-320x568-chromium-mobile-2026-08-07.png
new file mode 100644
index 0000000..0703d11
Binary files /dev/null and b/docs/ops/evidence/self-directed-loop-repractice-home-320x568-chromium-mobile-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-repractice-observation-320x568-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-repractice-observation-320x568-chromium-mobile-2026-08-07.png
new file mode 100644
index 0000000..31083a1
Binary files /dev/null and b/docs/ops/evidence/self-directed-loop-repractice-observation-320x568-chromium-mobile-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-repractice-observation-chromium-desktop-2026-08-07.png b/docs/ops/evidence/self-directed-loop-repractice-observation-chromium-desktop-2026-08-07.png
index 72481b2..0492d0f 100644
Binary files a/docs/ops/evidence/self-directed-loop-repractice-observation-chromium-desktop-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-repractice-observation-chromium-desktop-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-repractice-observation-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-repractice-observation-chromium-mobile-2026-08-07.png
index c2c6831..89d35ce 100644
Binary files a/docs/ops/evidence/self-directed-loop-repractice-observation-chromium-mobile-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-repractice-observation-chromium-mobile-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-repractice-prestart-320x568-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-repractice-prestart-320x568-chromium-mobile-2026-08-07.png
new file mode 100644
index 0000000..441bcfb
Binary files /dev/null and b/docs/ops/evidence/self-directed-loop-repractice-prestart-320x568-chromium-mobile-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-repractice-prestart-chromium-desktop-2026-08-07.png b/docs/ops/evidence/self-directed-loop-repractice-prestart-chromium-desktop-2026-08-07.png
index 237bfb0..5cb5325 100644
Binary files a/docs/ops/evidence/self-directed-loop-repractice-prestart-chromium-desktop-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-repractice-prestart-chromium-desktop-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-repractice-prestart-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-repractice-prestart-chromium-mobile-2026-08-07.png
index 3151fd1..0ba0e65 100644
Binary files a/docs/ops/evidence/self-directed-loop-repractice-prestart-chromium-mobile-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-repractice-prestart-chromium-mobile-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-review-prescription-320x568-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-review-prescription-320x568-chromium-mobile-2026-08-07.png
new file mode 100644
index 0000000..f472422
Binary files /dev/null and b/docs/ops/evidence/self-directed-loop-review-prescription-320x568-chromium-mobile-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-review-prescription-chromium-desktop-2026-08-07.png b/docs/ops/evidence/self-directed-loop-review-prescription-chromium-desktop-2026-08-07.png
index 1c1facd..8ee3995 100644
Binary files a/docs/ops/evidence/self-directed-loop-review-prescription-chromium-desktop-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-review-prescription-chromium-desktop-2026-08-07.png differ
diff --git a/docs/ops/evidence/self-directed-loop-review-prescription-chromium-mobile-2026-08-07.png b/docs/ops/evidence/self-directed-loop-review-prescription-chromium-mobile-2026-08-07.png
index 936c780..93e6e61 100644
Binary files a/docs/ops/evidence/self-directed-loop-review-prescription-chromium-mobile-2026-08-07.png and b/docs/ops/evidence/self-directed-loop-review-prescription-chromium-mobile-2026-08-07.png differ