vignette/docs/HANDOFF.md
Yun Chan af993961c4 G0~G8 완료 조건 재검증 판정을 핸드오프에 고정
원래 active goal 의 완료 선언 조건 네 가지를 실행으로 대조했다.
조건 2(학생 폐루프 실 DB browser), 3(SSOT 일치), 4(G7 비합성)는 충족이고,
조건 1은 G7 외부 GATE 때문에 부분 충족이다. G8 자체는 종료됐다.

재검증: API 914, gateway 58, executor 28, probe 11, helper launcher 37,
release agent 21, MeloTTS 16, local whisper 37, SSOT FAIL 0,
dashboard E2E 10/10, web api-types/typecheck/build, ruff clean.
G7 체커는 exit 1 을 유지해 합성으로 닫지 않았음을 증명한다.
2026-08-08 09:33:47 +09:00

19 KiB

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다.

완료 조건 판정 (2026-08-08 재검증)

원래 active goal의 완료 선언 조건 네 가지를 실행으로 대조한 결과다.

# 조건 판정 근거
1 G0~G8 각각의 코드 계약과 실제 runtime 증거 부분 충족 G0~G6·G8은 충족. G7만 외부 GATE로 열려 있다
2 학생 홈→회기→리뷰→처방→재연습→전이 폐루프가 실제 API/DB/browser 통과 충족 일회용 클론에서 returned-practice-db-closed-loop desktop 2/2 + mobile 2/2, route mock 0
3 current source 증거와 대시보드·TODO·backlog 일치 충족 SSOT checker FAIL 0, unit 5/5, dashboard E2E 10/10, count 33/2/0이 카드와 일치
4 G7을 가짜·합성 증거로 DONE 처리하지 않음 충족 check-g7-external-proof.py exit 1 유지, G7 카드는 GATE

재검증 수치: API 914 passed, gateway 58, executor 28, probe 11, helper launcher 37, release agent 21, MeloTTS 사이드카 16, local whisper 사이드카 37, SSOT FAIL 0, dashboard E2E 10/10, web api-types·typecheck·build 통과, ruff clean.

따라서 update_goal complete를 호출하지 않는다. 조건 1이 G7 때문에 미충족이고, 조건 4가 그것을 합성 증거로 메우는 것을 금지한다. G8 자체는 종료됐다(§3).

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…8b2dcom.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.py37/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.tsrandomUuid()로 통일. 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분 시간창으로 수집해야 한다.

& $py -X utf8 -B scripts/check-g7-external-proof.py `
  --voice-soak <soak.json> --runtime <runtime.json> `
  --topology <topology.json> --human-voice-gain <pack.json>
  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. 학생 자기주도 학습 폐루프

학습자 홈 추천 → 새 회기/사전 설정 → 실제 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. 최종 검증 명령과 현재 기준선

$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

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

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

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

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.pyEXPECTED_STATUS_COUNTSapps/web/e2e/dev-dashboard.spec.tsmetrics.done/metrics.doing함께 갱신한다.

격리 NAS 런타임 회기 E2E

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 증거가 우선이다.