# Vignette G0~G8 완료 핸드오프 > Updated: 2026-08-07 KST (G8 실제 rollback 종료 반영) > Workspace: `D:\workspace\vignette` > 이 문서는 다음 에이전트가 **G0~G8 아홉 목표 중 마지막으로 남은 G7 외부 게이트를 닫기 위한** 실행 기준이다. ## 0. 현재 상태 한 줄 요약 **G0~G6·G8은 internal DONE + 실제 runtime 증거로 종료됐다. 남은 것은 G7 외부 4-artifact 게이트 하나이며, 이건 코드로 풀 수 없고 사용자가 제공해야 하는 운영 자원·동의가 있어야 진행된다.** | Goal | 판정 | 완료 증거 | |---|---|---| | G0 Measurement Foundation | DONE | provenance census 29/29, unknown/missing/orphan/null/total violations 0 | | G1 Alliance Core | DONE | prompt 1.2 read-skew fix, prediction 24/24 ready, direction 9/9, error 0, recall 94.4%, precision 73.9% | | G2 Outcome Trajectory | DONE | benchmark accuracy/early-warning recall 1.0, false alert 0, 실 DB 10 sessions/30 measurements | | G3 Rupture & Repair | DONE | macro-F1 1.0, status 1.0, gaming 0, missed→partial→resolved 실 DB/API | | G4 Deliberate Practice | DONE | completed-session observer, actual API/DB attempts, teacher correction, returned-practice browser 폐루프 | | G5 Calibration & Transfer | DONE | actual transfer execution, independent/model-run/instrument/evidence provenance, DB/browser idempotency | | G6 Supervision & Research | DONE | safety event metadata-only projection, safety priority 1 > deterioration 2, raw detail 0 | | G7 Multimodal Alliance | **internal DONE · external GATE** | consent/withdrawal, synthetic soak 59/59, public unauth WSS 1008, focused 119 PASS | | G8 Continuous Improvement | **DONE** | 격리 NAS 실제 image rollback 2회 + executed receipt 2건 (아래 §3) | 대시보드 status count는 **33 done / 2 doing / 0 planned**다. ## 1. 작업 시작 전 반드시 읽을 파일 1. `AGENTS.md` 2. `docs/README.md` 3. `docs/HANDOFF.md` — 현재 문서 4. `docs/dev_dashboard.html` — 상태 SSOT 5. `docs/TODO.md` — 열린 작업 SSOT 6. `docs/ops/backlog-2026-06-26.md` 7. `docs/ops/outcome-os-g7-external-proof-readiness-2026-08-07.md` — **다음 작업의 핵심** 8. `docs/ops/nas-preview-g8-rollback-proof-runbook.md` 9. `docs/ops/nas-preview-deployment-evidence-2026-08-07.md` 10. `docs/ops/public-db-recovery-rehearsal-2026-08-07.md` `docs/archive/`는 현재 상태 근거로 읽지 않는다. ## 2. 절대 보존 경계 - Windows 11 + PowerShell 환경이다. 실행 전 OS·셸·도구 경로를 다시 확인한다. - 명령은 PowerShell 5.1 호환 문법을 기본으로 작성한다. NAS 원격 스크립트는 **UTF-8 base64 전달 방식**을 쓴다. (`$()`가 PowerShell에서 먼저 확장되는 것을 막는다.) - worktree는 큰 규모로 dirty하다. `git reset --hard`, `git checkout --`, 대규모 자동 포맷, 임의 삭제 금지. - Git commit/stage/push는 사용자가 요청하기 전에는 하지 않는다. - 공개 복구 DB, 원본 volume, pre-recovery container, dump, NAS named volume, 이전 API/Web 이미지를 삭제하지 않는다. - **로컬 포트 8001(공개 API) · 5173(공개 Web) · 55432(공개 DB) · 9099/9100(엔진)은 공개 런타임이다.** `scripts/dev-up.ps1`은 8000/5173 리스너를 정리하므로 공개 런타임이 떠 있는 동안 실행하지 않는다. - 비밀값, 이메일, cookie, 사용자 UUID가 포함된 raw response, `.env` 내용은 로그·증거·채팅에 출력하지 않는다. ## 3. G8 — 종료됨 (2026-08-07 21:04~21:12 KST) ### Gate6 계약 정정 (성립 불가능했던 이전 계약을 코드로 교체) 감사 대상 current API 이미지 `sha256:52e0e816…8b2d`가 `com.docker.compose.project=vignette-preview-20260807`, `service=api`, `version=2.20.1` **image label**을 갖고 있다. 따라서 "helper의 `com.docker.compose.*` key 0개"는 감사되지 않은 다른 이미지를 쓰지 않는 한 성립하지 않는다. 계약을 **key 부재 → 소속(membership)** 으로 바꿔 구현했다. - 코드: `scripts/launch-nas-preview-g8-helpers.py` - 테스트: `scripts/test_launch_nas_preview_g8_helpers.py` — **37/37** - 계약: image 상속 label을 baseline으로 읽고 container의 모든 compose label이 baseline과 같거나 선언된 격리 override인지 검사 · 최종 project ≠ target · 최종 service ∉ {api,web,db,proxy} · argv에 target project/service label 미주입(fake-runner) · exact container name/ID/`docker inspect` 증거 보존 - 런북: `docs/ops/nas-preview-g8-rollback-proof-runbook.md` ### 실제 실행 결과 | plan | receipt | 활성화된 API/Web 이미지 | |---|---|---| | `rollback-old` | `nas-g8-723eeef22eab05e63e3fafb0` | `79ec…4450` / `c530…2f28` | | `restore-current` | `nas-g8-2738846cf2cf4fbe8ce0fc26` | `52e0…8b2d` / `6fdb…f215` | - release gate·approval 각각 2회 멱등, lifecycle `executed`, artifact/approval/receipt binding, control-plane 분리 검증 - DB: `audit.ci_lifecycle_event` `rollback/executed` 2 · `audit.ci_human_approval_event` `authorize_rollback` 2 · `app.ci_release_gate` 2(전부 `pending_human_approval`, silent auto-promotion 0) · `ci_gate_artifact` 8 - HMAC journal 6 records, `previous_hash` 체인 전수 PASS, SHA256 `a5594feb…0690` - 최종 상태: health 3/3, OpenAPI 126, auth 401, Web 200, helper 0, listener 0, 비밀 env 파기 - **계획 이탈:** Windows SSH 터널 `18018→8018`은 NAS sshd가 `administratively prohibited`로 direct-tcpip를 거부해 쓸 수 없었다. **sshd 설정은 바꾸지 않았고**, 같은 격리 계약의 NAS-side probe 컨테이너에서 loopback으로 실행해 control-plane(8018)/preview(8088) origin 분리는 그대로 유지했다. 다음 에이전트도 NAS에서 포트 포워딩을 기대하지 말 것. - 기계 판독 증거: `docs/ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json` ## 4. 공개 서비스 복구 상태 — 이미 완료, 다시 망가뜨리지 말 것 공개 계정·회기 DB 복구는 2026-08-07 18:26 KST에 완료됐다. - Public Web `https://vignette.chanpaca.net` · Public API `https://api-vignette.chanpaca.net` (origin `127.0.0.1:8001`) - active DB container `vignette-dev-db`, volume `vignette_recovered_prod_20260807`, restart `unless-stopped` - 복구 집계: users 84, sessions 30, turns 705, Google users 16, orphan sessions 0 - health `environment=prod`, `db=true`, `engine=true`, `engine_mode=claude_cli`, dev-login disabled, unauth `/auth/me` 401 - 공개 OpenAPI 119 paths · watchdog `VignettePublicRuntimeWatchdog` 5분 주기 보안상 과거 `auth_session`은 복원하지 않았다. 사용자는 Google 재로그인이 필요하다. 보존물: pre-recovery container `vignette-dev-db-pre-recovery-20260807-182642`, pre-cutover dump `…20260807-091057Z.dump` SHA256 `6b84d5c8…af1f`, recovered dump `…20260807-092630Z.dump` SHA256 `d7bcc396…68ed`, post-cutover dump `…20260807-095511Z.dump` SHA256 `660695c8…65b88`. 공개 DB에는 current dirty source를 배포하지 않는다. ## 5. current source NAS 배포 — 완료 - URL `http://100.116.83.60:8088` · project `vignette-preview-20260807` · remote root `/volume1/docker/vignette-preview-20260807` - active release SHA `6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611` - API `sha256:52e0e816…8b2d` / Web `sha256:6fdbb646…f215` (previous: `79ec731f…0450` / `c530213f…62f28`) - DB volume `vignette-preview-20260807_pgdata` · pre-proof dump `vignette-preview-g8-preproof-20260807T110610Z.dump` SHA256 `92ed4736…1570f` - candidate browser E2E 108/108(634.578s), postdeploy SSE→DB review PASS - **G8 rollback/restore 이후 재검증:** 실제 브라우저 SSE→DB review 재실행 PASS(35.0s) 배포 중 첫 108 gate가 103/5로 fail-closed된 이력(G5 fixture drift, StrictMode retry fixture, `shell.css` manifest 누락)은 지우지 않는다. ### ⚠ 배포된 SHA는 비-secure origin 결함을 갖고 있다 (수정은 소스에만 있음) 배포 후 NAS 프리뷰(`http://100.116.83.60:8088`, 평문 HTTP·비-localhost)에 전체 회기 스펙을 돌려보니 24건이 실패했고, 원인은 단 하나였다. 페이지 스냅샷의 실제 예외는 `crypto.randomUUID is not a function`이다. 이 API는 **secure context(HTTPS 또는 localhost)에서만** 노출되는데 제품 코드 18곳이 fallback 없이 직접 호출했고, `RuptureRepairCard.tsx`는 렌더 시점(`useRef`)에 호출해 회기 리뷰 라우트 전체가 error boundary로 떨어졌다. 릴리스 게이트의 108/108은 **localhost 후보 스택**(secure context)에서 돌았기 때문에 이 경로를 한 번도 밟지 않았다. - 수정: `apps/web/src/lib/uuid.ts`의 `randomUuid()`로 통일. fallback도 `crypto.getRandomValues`를 우선 사용해 idempotency key의 예측 불가능성을 유지하고, Web Crypto가 아예 없을 때만 `Math.random`으로 내려간다. - 회귀: `apps/web/e2e/insecure-context-uuid.spec.ts` **6/6** (직접 호출 0건 검사 포함), typecheck·build 통과. - **아직 NAS에 배포하지 않았다.** 배포된 SHA `6030a677…c611`은 여전히 결함 빌드다. 다음 배포 때 release agent로 승격하고, 승격 뒤에는 NAS origin에서 회기 스펙을 다시 돌려 24건이 사라지는지 확인한다. - 교훈: candidate gate를 localhost에서만 돌리면 secure-context 전용 API 결함을 못 잡는다. 배포 대상과 같은 scheme/host 형태에서 최소 한 번은 회기 리뷰 라우트를 열어봐야 한다. ## 6. **다음 에이전트의 유일한 남은 목표 — G7 외부 종료 Gate** G7 내부 구현은 완료됐고 `scripts/check-g7-external-proof.py`는 현재 의도적으로 exit 1이다. 네 artifact를 **같은 public host, 겹치는 50분 시간창**으로 수집해야 한다. ```powershell & $py -X utf8 -B scripts/check-g7-external-proof.py ` --voice-soak --runtime ` --topology --human-voice-gain ``` 1. `scripts/soak-public-voice-websocket.py` v4 — 운영 Deepgram/OpenAI TTS, authenticated public WSS, **명시 동의 물리 마이크** 50분 양방향 `passed`. `--confirm-physical-capture` 없이는 장치 열거·캡처를 하지 않는다. 2. `scripts/capture-g7-runtime-evidence.py` — 같은 시간창의 관리자 endpoint worker/Uvicorn queue high-water. 3. `scripts/capture-g7-topology-evidence.py` — 같은 host·exact image의 50분 cgroup/proc/Docker/TCP high-water. 4. 독립 blind human voice-gain pack — 최소 30명 / 50회기 / 150 paired axis / blind evaluator 2인 / ICC(A,1) ≥ 0.75 · κ ≥ 0.70 · gain ≥ 0.01 · participant-cluster bootstrap 10,000회 95% CI lower > 0. 준비 상세는 `docs/ops/outcome-os-g7-external-proof-readiness-2026-08-07.md`. **사용자에게 받아야 하는 것 (코드로 대체 불가):** - 운영 Deepgram key·quota 사용 권한 - authenticated learner/admin session 또는 안전한 `storageState` 생성 협조 - 물리 마이크 50분 실행에 대한 **명시적 동의** - 실제 참가자·독립 평가자 운영 승인 합성 label이나 무동의 mic probe로 대체하지 않는다. `check-g7-external-proof.py` exit 0 전에는 G7 메인 상태를 DONE으로 바꾸지 않는다. ## 7. 학생 자기주도 학습 폐루프 ```text 학습자 홈 추천 → 새 회기/사전 설정 → 실제 Session → 종료 리뷰 → G4 처방 또는 G5 전이 의도 → 별도 재연습 회기 → completed-session observer → 실제 G4 attempt / G5 transfer execution → read-model reload → before/after·진행도·멱등 재확인 ``` 실제 DB-backed browser 증거: `apps/web/e2e/returned-practice-db-closed-loop.spec.ts` + `apps/web/e2e/harness/prepare-returned-practice-db.py`, desktop 2/2 + mobile 2/2, route mock 0, visible raw UUID 0, getUserMedia/enumerateDevices 0, horizontal overflow 0. 이 harness는 **disposable clone**(전용 DB container + 전용 API 포트 + 전용 vite 포트)에서만 실행한다. 공개 런타임 포트를 재사용하지 않는다. `--api-base-url`, `--database-url`, `--practice-internal-token`, `--transfer-internal-token`이 필요하고 API health의 `db`·`engine`이 모두 true여야 한다. 2026-08-07 재실행 절차(그대로 재현 가능): 1. `docker run -d --name vignette-g8-e2e-db-<날짜> -p 127.0.0.1:55439:5432` + `infra/db/init` 마운트, `POSTGRES_USER=vignette_owner` / `APP_DB_USER=vignette_app`. `app.ci_regression_dag_node`가 생기면 준비 완료. 2. 별도 엔진 게이트웨이를 **새 포트**(예: 9199)에 띄운다. 상주 게이트웨이의 claude 세션이 죽어 있으면 `engine=false`가 되므로 공개용 9099를 재사용하지 않는다. 3. uvicorn API를 8021에, vite를 5199에 띄우고 `ENGINE_URL`을 2번 게이트웨이로 지정한다. 4. harness 실행 → `E2E_RETURNED_PRACTICE_DB_CLOSED_LOOP=1` + `E2E_RETURNED_PRACTICE_FIXTURE`로 spec 실행. 5. 결과 기준: desktop 2/2 + mobile 2/2 = **4 passed**. 끝나면 컨테이너·프로세스를 모두 정리한다. G4/G5 핵심 production bug 수정은 보존한다. - G4 runtime SQL `digest(...)` → `app.digest(...)` - G5 JSONB bind는 `json.dumps` string이 아니라 dict/list object 전달 - route는 nested Pydantic suite를 `body.model_dump()` dict로 깨지 않고 typed object로 전달 ## 8. 최종 검증 명령과 현재 기준선 ```powershell $py = 'C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe' $ruff = 'C:\Users\encep\AppData\Local\hermes\hermes-agent\venv\Scripts\ruff.exe' ``` ### Backend full — 기준 API **898 passed**, gateway **58 passed** ```powershell Set-Location D:\workspace\vignette\apps\api & $py -X utf8 -B -m pytest -p no:cacheprovider app -q & $py -X utf8 -B -m pytest -p no:cacheprovider engine_gateway -q ``` ### G8 executor/probe/helper/release governance — 기준 28 / 11 / 37 / 21 ```powershell Set-Location D:\workspace\vignette & $py -X utf8 -B -m unittest scripts/test_serve_nas_preview_rollback_executor.py & $py -X utf8 -B -m unittest scripts/test_probe_nas_preview_g8_rollback.py & $py -X utf8 -B -m unittest scripts/test_launch_nas_preview_g8_helpers.py & $py -X utf8 -B -m pytest -p no:cacheprovider scripts/test_outcome_os_release_agent.py -q & $ruff check scripts/serve-nas-preview-rollback-executor.py scripts/test_serve_nas_preview_rollback_executor.py scripts/probe-nas-preview-g8-rollback.py scripts/test_probe_nas_preview_g8_rollback.py scripts/launch-nas-preview-g8-helpers.py scripts/test_launch_nas_preview_g8_helpers.py scripts/run-outcome-os-release-agent.py scripts/test_outcome_os_release_agent.py ``` ### Web contract/type/build ```powershell Set-Location D:\workspace\vignette\apps\web npm run check:api-types npm run typecheck npm run build ``` ### Dashboard SSOT + E2E — 기준 SSOT PASS(FAIL 0), unit 5/5, dashboard 10/10 ```powershell Set-Location D:\workspace\vignette & $py -X utf8 -B scripts/check-dev-dashboard-ssot.py & $py -X utf8 -B -m pytest -p no:cacheprovider scripts/test_dev_dashboard_ssot.py -q Set-Location D:\workspace\vignette\apps\web .\node_modules\.bin\playwright.cmd test e2e/dev-dashboard.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 --reporter=line ``` status count를 바꾸면 `scripts/check-dev-dashboard-ssot.py`의 `EXPECTED_STATUS_COUNTS`와 `apps/web/e2e/dev-dashboard.spec.ts`의 `metrics.done`/`metrics.doing`을 **함께** 갱신한다. ### 격리 NAS 런타임 회기 E2E ```powershell Set-Location D:\workspace\vignette\apps\web $env:PLAYWRIGHT_BASE_URL='http://100.116.83.60:8088' $env:PLAYWRIGHT_SKIP_WEB_SERVER='1' node.exe .\node_modules\@playwright\test\cli.js test e2e/session-layout.spec.ts e2e/session-persistence.spec.ts ` e2e/self-directed-learning-loop.spec.ts e2e/alliance-pulse.spec.ts e2e/outcome-trajectory.spec.ts ` e2e/rupture-repair.spec.ts e2e/deliberate-practice.spec.ts e2e/calibration-transfer.spec.ts ` e2e/supervision-research.spec.ts e2e/multimodal-alliance.spec.ts e2e/continuous-improvement-admin.spec.ts ` --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run --workers=1 --reporter=line ``` 전체 Playwright inventory는 610 tests / 44 files다. 실행 환경/API/DB를 정확히 맞추지 않고 fixture failure를 제품 failure로 오인하지 않는다. 같은 blocker가 두 번 반복되면 전체 재시도 대신 원인·증거·수정 계획을 먼저 보고한다. ## 9. 핵심 변경 파일 - `scripts/launch-nas-preview-g8-helpers.py` · `scripts/test_launch_nas_preview_g8_helpers.py` (신규, Gate6 계약) - `scripts/serve-nas-preview-rollback-executor.py` · `scripts/test_serve_nas_preview_rollback_executor.py` - `scripts/probe-nas-preview-g8-rollback.py` · `scripts/test_probe_nas_preview_g8_rollback.py` - `scripts/run-outcome-os-release-agent.py` · `scripts/test_outcome_os_release_agent.py` - `scripts/check-dev-dashboard-ssot.py` · `scripts/test_dev_dashboard_ssot.py` - `scripts/check-g7-external-proof.py` · `scripts/soak-public-voice-websocket.py` · `scripts/capture-g7-runtime-evidence.py` · `scripts/capture-g7-topology-evidence.py` - `apps/web/src/lib/uuid.ts` · `apps/web/e2e/insecure-context-uuid.spec.ts` (신규, 비-secure origin 결함 수정) - `apps/web/e2e/dev-dashboard.spec.ts` · `apps/web/e2e/returned-practice-db-closed-loop.spec.ts` · `apps/web/e2e/harness/prepare-returned-practice-db.py` - `docs/dev_dashboard.html` · `docs/TODO.md` · `docs/README.md` · `docs/ops/backlog-2026-06-26.md` - `docs/ops/nas-preview-g8-rollback-proof-runbook.md` · `docs/ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json` · `docs/ops/nas-preview-deployment-evidence-2026-08-07.md` ## 10. 최종 원칙 - source-only PASS를 runtime DONE으로 부르지 않는다. - health만 보고 배포 완료라고 하지 않는다. auth, OpenAPI, assets, browser SSE→DB review를 함께 본다. - synthetic control plane을 실제 NAS rollback으로 과장하지 않는다. - G7 물리 마이크·사람 평가를 무동의/합성 데이터로 대체하지 않는다. - current/previous images와 DB backup을 확인하기 전 destructive operation을 실행하지 않는다. - 성공보다 정직한 fail-closed 증거가 우선이다.