G8 마지막 게이트인 receipt-bound 실제 image rollback을 격리 NAS vignette-preview-20260807 에서 실행해 종료했다. Gate6 계약 정정: 감사 대상 current API 이미지가 com.docker.compose.project/service/version image label 을 갖고 있어 "helper 의 compose label 0개" 계약은 감사되지 않은 다른 이미지를 쓰지 않는 한 성립하지 않는다. 계약을 key 부재가 아니라 소속(membership) 으로 바꿔 launch-nas-preview-g8-helpers.py 에 구현했다. image 상속 label 을 baseline 으로 읽고 container 의 모든 compose label 이 baseline 과 같거나 선언된 격리 override 인지 검사하며, 최종 project 는 target 이 아니고 service 는 api/web/db/proxy 가 아니어야 한다. docker run argv 에 target label 을 주입하면 fake-runner 테스트가 먼저 깨진다 (37/37). 실행 결과: - rollback-old receipt nas-g8-723eeef22eab05e63e3fafb0 -> 79ec../c530.. - restore-current receipt nas-g8-2738846cf2cf4fbe8ce0fc26 -> 52e0../6fdb.. - release gate/approval 각 2회 멱등, audit.ci_lifecycle_event rollback/executed 2, audit.ci_human_approval_event authorize_rollback 2, silent auto-promotion 0 - HMAC journal 6-record 체인 검증, health 3/3, OpenAPI 126, auth 401, Web 200 - helper 0, listener 0, 비밀 env 파기. down/volume rm/prune 미실행, 공개 런타임 미접촉 - 계획했던 Windows SSH 터널은 NAS sshd 가 direct-tcpip 를 거부해 사용할 수 없어 sshd 설정 변경 대신 같은 격리 계약의 NAS-side probe 컨테이너로 실행했다 비-secure origin 크래시 수정: 배포된 NAS 프리뷰(평문 HTTP, 비-localhost)에 회기 스펙을 돌려 24건 실패를 확인했고 원인은 하나였다. crypto.randomUUID 는 secure context 전용인데 제품 코드 18곳이 fallback 없이 호출했고 RuptureRepairCard 는 렌더 시점 호출이라 회기 리뷰 라우트 전체가 error boundary 로 떨어졌다. 릴리스 게이트 108/108 은 localhost 후보 스택에서만 돌아 이 경로를 밟은 적이 없다. src/lib/uuid.ts 의 randomUuid() 로 통일하고 fallback 도 crypto.getRandomValues 를 우선 사용해 idempotency key 의 예측 불가능성을 유지했다. 회귀는 insecure-context-uuid.spec.ts 6/6 으로 고정했다(직접 호출 0건 검사 포함). 이 수정은 아직 NAS 에 배포하지 않았다. 검증: API 898, gateway 58, executor 28, probe 11, helper launcher 37, release agent 21, ruff clean, web api-types/typecheck/build, SSOT FAIL 0, SSOT unit 5/5, dashboard E2E 10/10, 학생 폐루프 실 DB 브라우저 4/4(일회용 클론), crypto 수정 후 기존 스펙 회귀 70/70, 복원된 NAS 실제 브라우저 SSE->DB 리뷰 PASS. 부수 발견(열린 항목): 공개 API 가 engine=false 로 degraded 인데 워치독이 이를 감지하지 못한다. engine 판정이 게이트웨이 /health 의 ok 만 보고 claude readiness probe 를 돌리지 않기 때문이다. 같은 .env 와 같은 CLI 로 새 게이트웨이를 다른 포트에 띄우면 즉시 ready 이므로 상주 프로세스의 세션만 죽은 형태다. TODO A절과 대시보드에 기록했다. 이 커밋은 파일 단위로 담겼다. 위 파일들에는 이전 세션의 미커밋 G0~G8 작업이 함께 들어 있으며, hunk 를 쪼개면 대시보드/체커/TODO 정합성이 깨져 SSOT 체커가 실패한다.
18 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다.
1. 작업 시작 전 반드시 읽을 파일
AGENTS.mddocs/README.mddocs/HANDOFF.md— 현재 문서docs/dev_dashboard.html— 상태 SSOTdocs/TODO.md— 열린 작업 SSOTdocs/ops/backlog-2026-06-26.mddocs/ops/outcome-os-g7-external-proof-readiness-2026-08-07.md— 다음 작업의 핵심docs/ops/nas-preview-g8-rollback-proof-runbook.mddocs/ops/nas-preview-deployment-evidence-2026-08-07.mddocs/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_eventrollback/executed2 ·audit.ci_human_approval_eventauthorize_rollback2 ·app.ci_release_gate2(전부pending_human_approval, silent auto-promotion 0) ·ci_gate_artifact8 - HMAC journal 6 records,
previous_hash체인 전수 PASS, SHA256a5594feb…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 APIhttps://api-vignette.chanpaca.net(origin127.0.0.1:8001) - active DB container
vignette-dev-db, volumevignette_recovered_prod_20260807, restartunless-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/me401 - 공개 OpenAPI 119 paths · watchdog
VignettePublicRuntimeWatchdog5분 주기
보안상 과거 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· projectvignette-preview-20260807· remote root/volume1/docker/vignette-preview-20260807 - active release SHA
6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611 - API
sha256:52e0e816…8b2d/ Websha256:6fdbb646…f215(previous:79ec731f…0450/c530213f…62f28) - DB volume
vignette-preview-20260807_pgdata· pre-proof dumpvignette-preview-g8-preproof-20260807T110610Z.dumpSHA25692ed4736…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.ts6/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>
scripts/soak-public-voice-websocket.pyv4 — 운영 Deepgram/OpenAI TTS, authenticated public WSS, 명시 동의 물리 마이크 50분 양방향passed.--confirm-physical-capture없이는 장치 열거·캡처를 하지 않는다.scripts/capture-g7-runtime-evidence.py— 같은 시간창의 관리자 endpoint worker/Uvicorn queue high-water.scripts/capture-g7-topology-evidence.py— 같은 host·exact image의 50분 cgroup/proc/Docker/TCP high-water.- 독립 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 재실행 절차(그대로 재현 가능):
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가 생기면 준비 완료.- 별도 엔진 게이트웨이를 새 포트(예: 9199)에 띄운다. 상주 게이트웨이의 claude 세션이 죽어 있으면
engine=false가 되므로 공개용 9099를 재사용하지 않는다. - uvicorn API를 8021에, vite를 5199에 띄우고
ENGINE_URL을 2번 게이트웨이로 지정한다. - harness 실행 →
E2E_RETURNED_PRACTICE_DB_CLOSED_LOOP=1+E2E_RETURNED_PRACTICE_FIXTURE로 spec 실행. - 결과 기준: desktop 2/2 + mobile 2/2 = 4 passed. 끝나면 컨테이너·프로세스를 모두 정리한다.
G4/G5 핵심 production bug 수정은 보존한다.
- G4 runtime SQL
digest(...)→app.digest(...) - G5 JSONB bind는
json.dumpsstring이 아니라 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.py의 EXPECTED_STATUS_COUNTS와
apps/web/e2e/dev-dashboard.spec.ts의 metrics.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.pyscripts/probe-nas-preview-g8-rollback.py·scripts/test_probe_nas_preview_g8_rollback.pyscripts/run-outcome-os-release-agent.py·scripts/test_outcome_os_release_agent.pyscripts/check-dev-dashboard-ssot.py·scripts/test_dev_dashboard_ssot.pyscripts/check-g7-external-proof.py·scripts/soak-public-voice-websocket.py·scripts/capture-g7-runtime-evidence.py·scripts/capture-g7-topology-evidence.pyapps/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.pydocs/dev_dashboard.html·docs/TODO.md·docs/README.md·docs/ops/backlog-2026-06-26.mddocs/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 증거가 우선이다.