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 @@
Vignette 커맨드센터팀장 리뷰 · dev status
내 차례 0 - 2026-08-29 KST + 2026-08-31 KST
@@ -300,10 +300,13 @@
project pulse -

지금 위치: 공개 정적 Web은 유지, Public API·DB는 OUTAGE. 이미지 fail-closed 후보·E2E와 C-001·REQ-008 사람 게이트가 남았다.

-

현재: 배포된 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 전환 승인을 받고, 실제 재부팅은 별도 승인으로 실행한다.

+

지금 위치: public 도메인은 별도 호스트(Cloudflare)에서 정상 서빙 중(이 PC와 분리 운영), NAS preview는 degraded. 이미지 fail-closed 후보·E2E와 C-001·REQ-008 사람 게이트가 남았다.

+

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를 순서대로 검증한다.

운영 복구·watchdog·배포 상세 보기 +

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를 대신하지 않는다.

@@ -320,7 +323,7 @@ Outcome clean E2E 126 · visual 15/15 Alliance G1 baseline 1.2 9/9 · 내부 DONE Tailnet 이전 확인 OK - Public API OUTAGE · 530/local refused + Public API(정식) 200 · prod · db/engine true 이전 OpenAPI 증거 · Public API 126 paths · G1~G8 Direct-runtime preflight APP ROLE OK Avatar decode 3 valid · 90 invalid @@ -709,6 +712,8 @@

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-stripgap: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가 별도로 필요하다.

래스터만 사용이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.
기능 우선메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.
@@ -1119,7 +1124,7 @@ Web typechecknpm run typecheckPassed Design SSOT / auth visualnpm 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=lineSSOT checker passed; login/onboarding light-dark desktop-mobile 1 passed; 14 core screens × 7 widths visual gate 14 passed. - Full Playwright E2E baselinenpm run e2e:parallel / npm run e2e:single-run / npm run e2e:list2026-08-29 현재 수집은 1158 tests / 64 files다. 이 숫자는 수집량이며 현 작업트리 전체 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는 범위가 다른 역사 기준선으로 보존한다. + Full Playwright E2E baselinenpm run e2e:parallel / npm run e2e:single-run / npm run e2e:list2026-08-31 현재 수집은 1182 tests / 66 files다. 이 숫자는 수집량이며 현 작업트리 전체 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는 범위가 다른 역사 기준선으로 보존한다. Refactor governance P1~P8ruff 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 PlaywrightBackend 400 passed, gateway 29 passed, web gates/build/audit passed, vulnerabilities 0, production duplication 1 clone/15 lines/0.03%, Playwright 215/215 passed. 상세 근거는 ops/refactor-governance-2026-07-15.md. API typegen SSOTnpm run check:api-typesPassed; FastAPI OpenAPI → src/lib/api.gen.ts stale check Outcome & Alliance OS G0py -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종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 완료. @@ -1216,10 +1221,10 @@ Public OAuth start/auth/config + /auth/login?provider=google2026-08-28 public auth config 200, Google configured true, allowed_email_domains=[], redirect URI https://api-vignette.chanpaca.net/auth/callback, dev-login disabled. 로그인 DOM은 모든 Google 계정을 명시하고 실제 Google 선택기에 yunchan@twentyoz.kryunchan8804@gmail.com이 함께 노출됐다. 기존 Google 계정 선택→callback→/admin 성공을 확인했다. 신규 Gmail 선택은 Vignette 계정 생성이므로 사용자 행동시점 확인 뒤 별도 실증한다. Persona auth boundaryGET /personas2026-06-30 복구 후 public unauth /personas는 401 not authenticated를 반환한다. Public loginauth.spec.ts --grep public login1 passed - Public runtime scripts · source pinstart/watch/boot-public-runtime*.ps1 · install-public-runtime-task.ps1 · register-boot-task.ps12026-08-29 최신 read-only 확인에서 두 task는 old detached dba9b75a… runtime을 계속 가리킨다. Docker daemon 부재로 runtime task는 실패했고 watchdog도 DB/API를 복구하지 못한다. 새 total-size/decode receipt 후보는 미배포이며, 후보 통합 GREEN·사용자 승인 전 task 재등록이나 runtime 재시작을 하지 않는다. 실제 Windows 재부팅 자동복구 smoke도 별도 운영 gate다. - Public API healthhttp://127.0.0.1:8001/health / https://api-vignette.chanpaca.net/health2026-08-29 최신 상태는 OUTAGE다. local 8001은 연결 거부, public API는 530이며 Docker Desktop Linux engine pipe가 없다. 정적 public Web 200은 API·DB GREEN을 뜻하지 않는다. 2026-08-28의 status=ok·db=true·engine=true와 voice exact 값은 과거 성공 증거로만 보존한다. - Public runtime current snapshothealth + provenance + Scheduled Tasks + Pages배포된 동적 런타임/task 기준선은 clean commit dba9b75a…·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을 하지 않는다. - Public/local/Tailnet login recoveryhttps://vignette.chanpaca.net/login / https://api-vignette.chanpaca.net/health / https://alpaca-home.taile93291.ts.net/login2026-08-28 Google callback→관리자·P20 실제 회기 폐루프는 유효한 과거 증거다. 2026-08-29에는 public API 530으로 신규 callback·관리자·G8 실DB E2E를 재검증할 수 없으므로 현재 운영 GREEN으로 재사용하지 않는다. public Web은 정적 로그인 화면만 200이다. + Public runtime scripts · source pinstart/watch/boot-public-runtime*.ps1 · install-public-runtime-task.ps1 · register-boot-task.ps1이 행은 이 PC의 Windows 런타임/task 기준이다. 2026-08-31 실측에서 이 PC에는 cloudflared 프로세스가 없고 VignettePublicRuntime/Watchdog 두 Scheduled Task는 Disabled이며 로컬 8001 listener도 없다 — 이 PC는 더 이상 public 호스트 역할을 하지 않는다. public 도메인은 별도 호스트에서 정상 서빙 중(health 200). 새 avatar fail-closed receipt 후보는 여전히 미배포이며, 사용자 승인 전 이 PC의 task 재등록·runtime 재시작을 하지 않는다. 실제 Windows 재부팅 자동복구 smoke는 별도 운영 gate다. + Public API healthhttp://127.0.0.1:8001/health / https://api-vignette.chanpaca.net/health2026-08-31 실측: public 도메인은 200·environment=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 기준 스냅샷으로, 오늘 실측이 이를 대체한다. + Public runtime current snapshothealth + provenance + Scheduled Tasks + Pages과거 이 PC 배포 기준선은 clean commit dba9b75a…·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을 하지 않는다. + Public/local/Tailnet login recoveryhttps://vignette.chanpaca.net/login / https://api-vignette.chanpaca.net/health / https://alpaca-home.taile93291.ts.net/login2026-08-28 Google callback→관리자·P20 실제 회기 폐루프는 유효한 과거 증거다. 2026-08-31 실측(Playwright chromium): public 로그인 화면 폐루프 1 passed, 데스크톱/모바일 HTTP 200, Google OAuth redirect_uri=https://api-vignette.chanpaca.net/auth/callback 확인, dev-login 차단, 예상된 /auth/me 401 외 콘솔/페이지 에러 0 — 읽기 전용 실증 완료(증거). 실제 Google 계정 로그인→callback→관리자·P20 실회기·턴 생성은 production DB mutation이라 소유자 승인·계정 세션(storageState)이 필요해 여전히 열린 게이트다. Local 5175 loginPLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 auth.spec.tsdesktop/mobile passed Learner/readiness E2Elearner.spec.ts + readiness.spec.ts desktop/mobile14 passed Learner growth headeruc-learner-home-dashboard.spec.ts --grep "성장 지표 라포 헤더"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에는 아직 미배포다. diff --git a/docs/guides/architecture.md b/docs/guides/architecture.md index 7f8d304..2f68155 100644 --- a/docs/guides/architecture.md +++ b/docs/guides/architecture.md @@ -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), diff --git a/docs/guides/local-development.md b/docs/guides/local-development.md index a48a399..281ec97 100644 --- a/docs/guides/local-development.md +++ b/docs/guides/local-development.md @@ -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. 웹(프런트엔드) 실행 diff --git a/docs/guides/testing.md b/docs/guides/testing.md index ebf26a9..ce63397 100644 --- a/docs/guides/testing.md +++ b/docs/guides/testing.md @@ -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 결과를 구분해 적는다. diff --git a/docs/ops/backlog-2026-06-26.md b/docs/ops/backlog-2026-06-26.md index 769ab89..eb4bc5e 100644 --- a/docs/ops/backlog-2026-06-26.md +++ b/docs/ops/backlog-2026-06-26.md @@ -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는 개발 워크스테이션). 2026-08-31 재실측: 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)를 실증했다(증거 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