57 KiB
Vignette G0~G8 완료 추진 핸드오프
Updated: 2026-08-09 KST (G8 clean-head NAS-origin 종료 · 남은 핵심은 G7 external proof) Workspace:
D:\workspace\vignette이 문서는 다음 에이전트가 G0~G8 아홉 목표 중 마지막 G7 외부 게이트를 닫기 위한 실행 기준이다.
0. 현재 상태 한 줄 요약
G0~G6의 내부 구현·증거와 G8 clean-head NAS-origin runtime은 종료됐다. G8은 source HEAD
61a41d1f…6af·tree 87dec55d…3b77·archive 4d15d055…119d4d로 candidate 112/112와 actual NAS-origin
112/112를 모두 통과했다. 앞선 fail-closed·verified rollback 이력과 previous 6030a677…c611은 보존했다. G7 공개 런타임은
detached-clean b34623f3…·tree 32cc85c7…로 fresh 승격했고 OpenAPI 126, local Whisper/MeloTTS, Uvicorn queue 4,
API/cloudflared provenance receipt와 source-pinned Scheduled Task result 0을 확인했다. 인증 WSS·runtime·Windows topology의
30초 무마이크 rehearsal도 통과했다. 남은 것은 명시 동의 물리 마이크 3,120초·공통 3,000초 high-water,
독립 human pack과 canonical checker exit 0이다.
| 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 b34623f3…·OpenAPI 126·local voice, no-mic rehearsal 3 legs × 7 samples |
| G8 Continuous Improvement | DONE · CLEAN-HEAD NAS VERIFIED | 실제 image rollback 2회 + executed receipt 2건, source/archive 결속, candidate 112/112, actual NAS-origin 112/112 |
대시보드 status count는 33 done / 2 doing / 0 planned다.
G8 배포 source 기준선은 branch master, HEAD 61a41d1f08239b5f9e99cfae42a206be250416af·tree
87dec55daf0a22ca5c2e489cd803a2f731e63b77이며 2회 동일 archive SHA는
4d15d055d74f33f1fa2ff470afbf900d565ab238b19c79df34ca7ba240119d4d다. 공유 worktree의 후속 변경은 이
배포 source에 포함되지 않으므로 G8 완료 증거와 섞지 않는다.
이 문서가 인계된 정확한 중단점
- G8 clean-head execute는 격리 NAS preview만 변경했고 공개 API/DB·물리 마이크는 건드리지 않았다. source HEAD/tree와 archive 2회 결정성, fresh dump, exact images, candidate/actual-origin 결과를 모두 결속했다.
- G8 종료 뒤 공개 Web은 Pages production deployment
7bd59055…로 갱신했다. 실제 사용자 회기에서 literal[NAME]0,시간 만료, 모바일 44px·자동 스크롤 보정을 브라우저로 확인했다. 공개 API는 아래 G7 clean promotion에서 별도로 fresh 승격했다. - active goal은 계속
active다. 목표는 “문서 작성”이 아니라 G0~G8 아홉 목표를 실제 runtime과 E2E로 모두 닫는 것이다. G8은 종료됐고 G7 canonical checker exit 0이 없으면goal complete를 호출하지 않는다. - 마지막 완료 작업은 G8 source HEAD
61a41d1f…6af·tree87dec55d…3b77·archive4d15d055…119d4d의 candidate 112/112와 actual NAS-origin 112/112다. API/Webd5021950…e4b1/9796c092…4b36, health ok·db/engine true·auth 401·OpenAPI 126, fresh dump36ec8748…24db8을 확인했다. - G7 Windows topology mode, 3,000초 공통 시간창, canonical checker-bound exit와 공개 런처의 local
Whisper/MeloTTS lifecycle·exact readiness·Uvicorn queue·fresh API/cloudflared provenance 계약은 코드와 공개 runtime에서
확인했다. detached-clean
b34623f3…·tree32cc85c7…의 receipt9f8d1941…a21bf가 passed이고, OpenAPI 126·/admin/voice-runtime·local_whisper/small·melotts/melotts-korean·queue 4가 공개 경로와 일치한다. - public task source-pin P0 완료: watchdog·로그온 boot는 detached clean release의 commit/tree와
watchdog|boot/start script SHA를 mutation 전에 검증한다. 두 Scheduled Task를 같은
b34623f3…stable root에 재등록하고 명시 실행해LastTaskResult=0·Ready와 working directory pin을 확인했다. 실제 Windows 재부팅 뒤 자동복구 smoke는 별도 운영 게이트로 남는다. - 인증 WSS ready/ping/close 1000, runtime 7 samples, Windows topology 7 samples의 30초 무마이크 rehearsal을 통과했다.
voice/runtime/topology evidence에는 UUID/email literal이 없고
physical_capture=false다. 다음 mutation은 사용자가 정확한 장치를 선택하고 명시 동의한 뒤의 3,120초 마이크 캡처다. G8 NAS는 새 material milestone이 생기기 전까지 읽기 전용 기준선으로 둔다.
완료 조건 판정 (2026-08-09 재검증)
원래 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, dashboard E2E 10/10, count 33/2/0이 카드와 일치 |
| 4 | G7을 가짜·합성 증거로 DONE 처리하지 않음 | 충족 | check-g7-external-proof.py exit 1 유지, G7 카드는 GATE |
재검증 수치: API 921 passed, gateway 58, executor 28, probe 11, helper launcher 37, release agent 30, current G7 six-suite 87/87, runtime sampler 4/4, API voice 통합 71, MeloTTS 사이드카 16, local whisper 사이드카 38, SSOT FAIL 0, dashboard E2E 10/10, web api-types·typecheck·build 통과, ruff clean.
current-source fixture 묶음은 16:49 KST 단일 실행에서 **120/120 PASS(92.0s)**했다. dev dashboard file URL은 실행 cwd가
아니라 spec의 import.meta.url로 repo root를 찾으며, session-review fixture는 alliance pulse까지 소유한다. 관리자
live health 테스트는 React StrictMode 중복 조회의 첫 응답을 고정하지 않고 화면이 실제 반영한 성공
/admin/users 스냅샷과 KPI를 결속한다. 그 뒤 dev-dashboard spec/HTML이 변경됐고 최신 dashboard 10/10만 따로
재검증됐다. 따라서 current tree의 정직한 판정은 110 불변 + 10 최신 분할 GREEN이며, 새 clean commit에서
같은 120건을 한 번 재실행해 archive/source 결속 뒤의 결과를 고정한다.
따라서 update_goal complete를 호출하지 않는다. 조건 1이 G7 external proof 때문에 미충족이고,
조건 4가 G7을 합성 증거로 메우는 것을 금지한다. G8은 clean-head NAS-origin까지 종료됐다(§3·§5).
G0~G8 목표별 인수 기준
| Goal | 원 계획의 종료 조건 | 현재 인정 가능한 증거 | 다음 에이전트가 최종 종료 전에 할 일 |
|---|---|---|---|
| G0 | 100% provenance, evaluator/client 누수 0, Python/TS/DB schema conformance, 기존 회기 무회귀 | current producer census 29/29, 위반 0, migrations 14~16 2회 멱등 | current HEAD 전체 backend/schema 회귀와 최종 census를 다시 실행 |
| G1 | goal/task/bond 독립 저장, 축별 UI, gold 근거 span, 평가 실패 fail-closed | prediction 24/24 ready, 방향 9/9, error 0, recall 94.4%, precision 73.9%, pre/mid/post UI | current HEAD의 alliance desktop/mobile·DB-backed session 경로 재실행 |
| G2 | 5회기 연속성, 조기 경보 recall, false alert/uncertainty, synthetic 비임상 경계 | accuracy/recall 1.0, false alert 0, 실제 DB 10 sessions/30 measurements | current HEAD outcome trajectory API/UI 회귀 재실행 |
| G3 | rupture 유형 benchmark, missed/partial/resolved E2E, gaming/암기 방지 | macro-F1/status 1.0, gaming 0, 실제 DB/API missed→partial→resolved | text/SSE 회기 및 rupture UI desktop/mobile 재실행 |
| G4 | 모든 코칭 카드의 실행 재연습, 전후 근거 저장, unseen 전이 전 mastery 금지 | 실제 API/DB attempt·observer·teacher correction, browser route mock 0 | disposable DB에서 returned-practice desktop/mobile 4/4와 멱등 재확인 |
| G5 | 자기평가 잠금, calibration 감소, unseen transfer 유지, subgroup drift | actual transfer execution과 independent/model-run/instrument/evidence provenance | G4와 같은 disposable DB에서 actual POST·read reload·1→1 멱등 재확인 |
| G6 | 3-click queue, 원장 drilldown, 버전 재현, 역할/RLS/audit | safety metadata-only, safety priority 1 > deterioration 2, raw detail 0 | current HEAD producer/store/API와 supervisor UI 회귀 재실행 |
| G7 | 시간 정렬, 물리 마이크+공개 WSS 장시간 E2E, text-only 대비 음성 이득, 동의/삭제 정책 | 내부 계약·공개 clean runtime·인증 WSS 무마이크 rehearsal 완료. canonical checker는 의도적으로 미실행/미종료 | 장치 선택·명시 동의→mic 3,120초→동시 runtime/topology→독립 human pack→checker exit 0 |
| G8 | source→draft→red-team→benchmark→catalog 재현, 누수/PII/무근거 0, 자동 calibration+rollback, incident→test/backlog 환류 | agentic worker·human gate·실제 rollback/restore receipt 2건, clean-head candidate/NAS-origin 112/112 | 종료. 새 material milestone 전까지 NAS 기준선 보존 |
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에서 먼저 확장되는 것을 막는다.) - G8 배포 source는
master/61a41d1f08239b5f9e99cfae42a206be250416af이며 공유 worktree의 후속 변경은 이 clean release와 분리한다. stage/commit 시git add -A를 쓰지 않고 명시적 경로만 다룬다. - clean-head 구현은 unit 30/30, G7 runner/checker/topology는 86/86, G7 public launcher/sidecar는 80/80, public task source pin은 provenance 11개를 포함한 combined 22/22, timestamp·voice 계약을 포함한 API는 921/921와 KST 실DB browser 1/1, G8 candidate/NAS-origin은 각각 112/112, SSOT는 FAIL 0와 dashboard E2E 10/10을 통과했다. 다음 에이전트는 최초 diff review 뒤 동일한 focused gate를 다시 실행하고, 서로 다른 lane의 파일을 누락한 채 clean commit을 만들지 않는다.
- 기존·watchdog 생성 엔진 로그 4개와
apps/web/test-results/의 PNG 2개는 commit에서 제외하고 보존한다. 신규apps/api/app/test_session_read_model.py와docs/ops/evidence/g8-clean-head-material-milestone-2026-08-09.json은 이번 목표 산출물이므로 clean source에 포함해야 한다. 시작 즉시git status --short로 다시 확인하고git reset --hard,git checkout --, 대규모 자동 포맷, 임의 삭제는 금지한다. - Git commit/stage/push는 사용자가 요청하기 전에는 하지 않는다.
- 공개 복구 DB, 원본 volume, pre-recovery container, dump, NAS named volume, 이전 API/Web 이미지를 삭제하지 않는다.
- 로컬 포트 8001(공개 API origin) · 55432(공개 DB) · 9099/9100(엔진)은 공개 런타임이다.
공개 Web은 Cloudflare Pages이고 5174는
vnet.18ka.net용 로컬 preview다.scripts/dev-up.ps1은 런타임 리스너를 정리할 수 있으므로 공개 런타임이 떠 있는 동안 실행하지 않는다. - 비밀값, 이메일, cookie, 사용자 UUID가 포함된 raw response,
.env내용은 로그·증거·채팅에 출력하지 않는다.
3. G8 receipt + clean-head NAS-origin — 종료됨
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에 완료됐다. 2026-08-09 14:03 KST에는 Docker Desktop이
중단돼 DB listener가 사라지면서 public health가 일시적으로 db=false가 됐고, 18:02:55 KST에는 Docker Desktop
UI가 active DB container에 직접 stop을 보내 같은 증상이 재발했다. 두 번 모두 보존된 동일 컨테이너·볼륨만
재기동해 복구했다. 새 컨테이너·볼륨 생성이나 데이터 복원 덮어쓰기는 없었다.
- 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 - cutover 기준 복구 집계: users 84, sessions 30, turns 705, Google users 16, orphan sessions 0
- 2026-08-09 18:05 KST 재기동 후 owner read-only 집계: users 356, sessions 193, turns 726, Google users 16,
Google-owned sessions 31. 조회는
BEGIN READ ONLY뒤ROLLBACK했고 PII를 출력하지 않았다. - health
environment=prod,db=true,engine=true,engine_mode=claude_cli, dev-login disabled, unauth/auth/me401 - 2026-08-09 G7 promotion snapshot: local/public health는
status=ok, db=true, engine=true, Google OAuth start는 302→accounts.google.com, 공개 OpenAPI는 126 paths이고/admin/voice-runtime이 존재한다./voice/health는local_whisper/small·melotts/melotts-korean·WS queue 4를 보고한다. - DB 컨테이너는
vignette_recovered_prod_20260807named volume과unless-stopped를 유지한다. API/cloudflared는 detached-cleanb34623f3…·tree32cc85c7…에서 실행되고 receipt9f8d1941…a21bf가 passed다. watchdog·로그온 task도 같은 stable root/commit/tree/script SHA에 pin해 명시 실행 결과 0·Ready를 확인했다. 실제 Windows 재부팅 뒤 자동복구 smoke만 남아 있다.
보안상 과거 auth_session은 복원하지 않았다. 사용자는 Google 재로그인이 필요하다.
보존물: pre-recovery container vignette-dev-db-pre-recovery-20260807-182642과 원본/recovery Docker volumes,
그리고 2026-08-09 fresh custom dump
D:\workspace\vignette-backups\vignette-dev-db-vignette-20260809-122235Z.dump
(SHA256 f1fd569c…5f64, 7,458,327 bytes)다.
과거 증거에는 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이 기록돼 있다. 이 세 과거 dump의 현재 위치는 별도로 재확인하지 않았으므로 삭제로 단정하지 말고
off-host/NAS/다른 경로를 확인하기 전 restore·cleanup을 금지한다. NAS의 pre-proof/pre-clean-head dump는 §5 기준으로
실제 존재와 hash를 재확인했다.
공개 DB에는 current dirty source를 배포하지 않는다.
5. NAS 배포 기준선 — 2026-08-09 clean-head DONE
- URL
http://100.116.83.60:8088· projectvignette-preview-20260807· remote root/volume1/docker/vignette-preview-20260807. - source HEAD
61a41d1f08239b5f9e99cfae42a206be250416af· tree87dec55daf0a22ca5c2e489cd803a2f731e63b77· 2회 동일 active archive4d15d055d74f33f1fa2ff470afbf900d565ab238b19c79df34ca7ba240119d4d. - API
sha256:d502195021beafe869c555f3ec80269426062ef4586dc15a3e049f9c8660e4b1/ Websha256:9796c0925f6a984cb846b61a4f13fda4d403bda7b3a2b6232b728cebbcdc4b36exact image가 실행 중이다. - candidate session E2E 112/112, 실제 NAS 평문 origin browser review 112/112(11 specs,
desktop/mobile/single-run), health
status=ok·db=true·engine=true, auth 401, OpenAPI 126. - fresh custom dump
vignette-preview-pre-clean-head-retry-61a41d1f-20260809T112038Z.dump, SHA25636ec8748cf28c520d9489d108dee9d4a154e6260f6996b4da3e0fe3b8a624db8, 1,097,100 bytes, TOC 1,738 / TABLE DATA 129를 보존했다. - previous active
6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611과 이전 images, named DB volume을 삭제하지 않았다.
배포 중 첫 108 gate가 103/5로 fail-closed된 이력(G5 fixture drift, StrictMode retry fixture, shell.css manifest 누락)은
지우지 않는다.
과거 비-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 통과. - UUID와 portable SHA-256 수정은 archive
4d15d055…119d4d로 NAS에 배포했고 actual-origin 112/112를 통과했다. - 교훈: candidate gate를 localhost에서만 돌리면 secure-context 전용 API 결함을 못 잡는다. 배포 대상과 같은 scheme/host 형태에서 최소 한 번은 회기 리뷰 라우트를 열어봐야 한다.
6. 다음 실행 순서 — G8 종료, G7 외부 종료
6.1 G8: clean-head 릴리스 경로 — DONE
scripts/run-outcome-os-release-agent.py의 명시적 clean-head 경로는 구현됐다. 기존 patch mode를 기본값으로
보존하며, tracked-clean source worktree의 exact HEAD/tree/archive만 candidate로 사용한다. 수동 복사나 NAS 직접
빌드는 계속 금지한다.
구현된 --source-mode clean-head 계약은 다음과 같다.
- tracked dirty가 있으면 fail-closed. untracked 로그·테스트 결과는 archive에서 제외한다.
- exact Git HEAD, tree,
git archiveSHA를 증거에 기록한다. - 같은 HEAD의 archive를 두 번 만들어 SHA가 동일한지 검증한다.
- candidate는 그 archive만 풀어 만들고 patch/manifest를 암묵 적용하지 않는다.
- 기존 preflight·API full·Web type/build·release E2E·DB snapshot·rollback 절차는 그대로 유지한다.
- Vite source 전용
e2e/insecure-context-uuid.spec.ts는 별도 localhost gate(5198)로 12/12 실행한다. UUID fallback 6건과 SHA-256 fallback·직접 호출 금지 6건을 함께 고정하고, production candidate/NAS-origin은 실제 배포 번들에서 동작하는 11개 회기 스펙만 실행한다. - unit/fake runner가 tracked dirty, archive SHA drift, evidence binding, patch-mode 무회귀를 검증해야 한다.
검증: release-agent unit 30/30, source-only insecure-context gate 12/12, Ruff, py_compile, git diff --check PASS.
최종 실행은 detached clean HEAD 61a41d1f…6af에서 candidate 112/112와 actual NAS-origin 112/112를 통과했다.
이하 preflight와 실패 기록은 최종 성공에 이르기까지의 역사 증거이며 다시 실행할 지시가 아니다.
역사 기록: 첫 clean commit preflight — include 52 / exclude 6
2026-08-09 최종 경로 목록 SHA-256은 ordinal 정렬된 UTF-8 LF 경로를 줄바꿈으로 연결하고 마지막 개행 없이 계산해
include 4d94694ccf9def6945272c61f673cac0eaa9fb59f4f9b6c165d661d819f891a1, exclude
1ff8c26732772df6b5857bf84de01a53d7da9c3b7d52df08938e77f3a527f022였다. 아래 목록은 첫 clean commit 범위를
감사하기 위해 보존한 이력이며 현재 worktree staging 목록이 아니다.
apps/api/app/config.py
apps/api/app/routes/voice.py
apps/api/app/services/session_metrics.py
apps/api/app/services/voice.py
apps/api/app/session_read_model.py
apps/api/app/test_session_read_model.py
apps/api/app/test_voice_service.py
apps/api/app/test_voice_ws.py
apps/api/requirements.txt
apps/web/e2e/admin.spec.ts
apps/web/e2e/dev-dashboard.spec.ts
apps/web/e2e/self-directed-learning-loop.spec.ts
apps/web/e2e/session-mvp.spec.ts
apps/web/e2e/session-persistence.spec.ts
apps/web/e2e/session-review-fixture.ts
apps/web/e2e/session-review.spec.ts
apps/web/index.html
docs/HANDOFF.md
docs/TODO.md
docs/decisions/local-voice-stack.md
docs/dev_dashboard.html
docs/guides/local-development.md
docs/guides/testing.md
docs/ops/backlog-2026-06-26.md
docs/ops/evidence/g8-clean-head-material-milestone-2026-08-09.json
docs/ops/nas-preview-g8-rollback-proof-runbook.md
docs/ops/outcome-os-g7-external-proof-readiness-2026-08-07.md
docs/ops/public-runtime-watchdog.md
scripts/boot-public-runtime.ps1
scripts/capture-g7-topology-evidence.py
scripts/check-dev-dashboard-ssot.py
scripts/check-g7-external-proof.py
scripts/install-public-runtime-task.ps1
scripts/local-whisper-stt-server.py
scripts/melotts-server.py
scripts/probe-public-voice-sidecars.py
scripts/register-boot-task.ps1
scripts/run-g7-external-proof-window.py
scripts/run-outcome-os-release-agent.py
scripts/start-local-whisper-stt.ps1
scripts/start-public-runtime.ps1
scripts/test_g7_external_proof.py
scripts/test_g7_topology_evidence.py
scripts/test_local_whisper_stt_server.py
scripts/test_melotts_server.py
scripts/test_outcome_os_release_agent.py
scripts/test_public_runtime_watchdog_provenance.py
scripts/test_public_voice_sidecar_probe.py
scripts/test_run_g7_external_proof_window.py
scripts/test_start_public_runtime_contract.py
scripts/watch-public-runtime-hidden.vbs
scripts/watch-public-runtime.ps1
아래 6개와 ignored infra/.env.nas-preview는 절대 stage하지 않는다. NAS 실행 시 env는
--nas-env-file D:\workspace\vignette\infra\.env.nas-preview로 외부 주입한다.
apps/api/engine.public.err.log.2026-08-07T2200.bak
apps/api/engine.public.err.log.20260809-160042.bak
apps/api/engine.public.out.log.2026-08-07T2200.bak
apps/api/engine.public.out.log.20260809-160042.bak
apps/web/test-results/g7-voice-consent-desktop.png
apps/web/test-results/g7-voice-consent-mobile.png
2026-08-09 clean-head 실행 결과 — NAS mutation 0
- dry-run
D:\workspace\vignette-recovery\g8-clean-head-dry-run-20260809.json:ok=true, active6030a677…c611, desired archive6b1b15bd…a3f7,deployment_mutated=false. - execute 1
...\g8-clean-head-execute-20260809.json: Windows Git archive의 CRLF 때문에 생성 API 타입 byte check가 실패했다. archive SHA 검증 후 candidate의 생성 타입/셸 스크립트만 LF로 정규화하도록 controller를 수정했다. NAS mutation 0, rollback 불필요. - execute 2
...\g8-clean-head-execute-20260809-r2.json: API/types/typecheck/build/candidate stack은 통과했고 Playwright는 109 passed / 5 failed였다. source-only UUID 스펙 4건은 production build에서/src/lib/uuid.ts를 import할 수 없는 gate 배치 오류라 별도 Vite gate로 분리했다. 남은 1건은 아래 제품 버그다. NAS mutation 0, rollback 불필요. 동일 전체 execute를 아직 다시 시도하지 않았다.
2026-08-09 clean-head 5221f79e 실행 결과 — NAS mutation 0
- exact include 52개를 Yun Chan으로 커밋했다. HEAD
5221f79e…1c69, treee4f15001…308b, 2회 동일 archive1109bf86…0f4b(482,662,400 bytes)를 detached clean worktree에서 고정했다. - 새 milestone과 dry-run은 active
6030a677…c611→ desired1109bf86…0f4b,deployment_mutated=false로 통과했다. 실행 직전 NAS custom dump6f4b95a7…b529f(1,015,222 bytes, TOC 1,752 / TABLE DATA 129, 전용 pgdata volume)를 생성했다. - execute는 API 921, API types, typecheck, build, insecure-context 6/6, candidate stack과 DB-backed
single-run 후반부까지 통과했지만 110 passed / 2 failed에서 승격 전에 중단됐다. 실패는 제품 화면이 아니라
React 부트가 실제로 비어 있으면 진단을 유지한다의 desktop/mobile 두 복제였다. - 원인은 테스트가 Vite 개발 entry
/src/main.tsx만 차단해 production candidate의 hashed entry/assets/index-*.js를 차단하지 못한 환경 계약 누락이다. 두 entry를 함께 차단하도록 수정했고 Vite source 2/2와 production preview 2/2를 각각 통과했다. 전체 execute를 즉시 반복하지 않고 이 수정의 새 clean commit·archive를 다시 결속한 뒤 한 번만 재실행한다. - 후속 clean commit
21461ab3…6fde, tree5c5f12a8…524e, 2회 동일 archive20d49694…fa1aexecute는 candidate 112/112를 통과하고 NAS 새 API/Web imaged5021950…/376a3aa8…까지 올렸다. 그러나 실제 NAS-origin postdeploy의 G4 deliberate-practice desktop/mobile 2건이 110/112에서 실패해 active-state commit 전에 이전52e0e816…/6fdbb646…로 자동 rollback했고 health·auth 401·OpenAPI 126·G0~G8 route와 image ID를 재검증해 rollbackverified로 종료했다. - focused exact-image 평문 origin에서 요청이 API까지 가지 않고 일반 오류로 끝나는 것을 재현했다. 원인은
문장 원문을 보내지 않기 위한 fingerprint가
crypto.subtle.digest에만 의존해 insecure origin에서 예외가 난 것이다. 원문 외부 전송 없이 portable SHA-256 fallback을 추가하고 표준 digestc06db0f0…62aa를 payload에서 exact 검증했다. localhost secure origin 2/2와 실제 Tailnet insecure origin 2/2가 같은 digest로 통과했다. 이 수정의 새 clean commit만 다시 결속해 전체 execute를 재개한다.
P0 수정 완료 — 회기 timestamp UTC 전송과 KST 표시 날짜
- 최초 실패 스펙:
session-persistence.spec.ts의persists AI tutor coaching history through reload @single-run. - 코칭 생성·DB 저장·reload 후
C마커까지는 성공했지만, 수정 전 화면이9:03:59 / 543:59경과로 계산해회기 시간이 종료됐어요모달을 띄웠고 그 모달이 코칭 마커 클릭을 가로막았다. - 원인:
apps/api/app/session_read_model.py::iso()가datetime.fromtimestamp(ts).isoformat(timespec="seconds")로 timezone 없는 문자열을 반환한다.apps/web/src/pages/Session.tsx::elapsedFromSession()은Date.parse(detail.started_at)로 이를 브라우저 로컬 KST로 해석한다. Linux API UTC와 KST 브라우저 사이에서 약 9시간 오차가 생긴다. - 수정:
session_read_model.iso()와 dashboard용session_metrics.iso_datetime()은 UTC+00:00을 반환한다. 회기 리뷰 달력 날짜는 서버 timezone이 아니라 고정 KST(+09:00)를 사용한다. - backend 관련 65 passed, 후속 voice 계약을 포함한 API 전체 921 passed, Ruff·compile·web typecheck가 통과했다.
- 고유 Compose project/volume의 실제 DB/API/engine/browser에서 KST context로 focused 1/1 PASS했다. UTC suffix,
status=active,ended_at=null, 회기 나이 10분 미만, 화면00:00-09:59, timebar/dialog 0, coach mark 실제 클릭과 DB history/source-pack dialog를 모두 검증했다. 종료 뒤 container/volume/listener는 0이다. - 테스트 강제 클릭이나 모달 닫기 우회는 사용하지 않았다. 첫 clean source
5221f79e…1c69에는 제품 수정과 controller 변경이 포함됐고, production entry 차단 회귀 2건의 테스트 수정만 후속 clean commit으로 다시 고정한다.
6.2 G8: NAS preview 배포와 실제 origin E2E — DONE
전용 NAS preview만 대상으로 백업→dry-run→배포→검증했고 공개 DB·공개 API는 건드리지 않았다. 아래 성공 조건을 전부 충족했다.
최종 fresh backup은
/volume1/docker/vignette-preview-20260807/backups/vignette-preview-pre-clean-head-retry-61a41d1f-20260809T112038Z.dump,
SHA-256 36ec8748cf28c520d9489d108dee9d4a154e6260f6996b4da3e0fe3b8a624db8,
1,097,100 bytes, TOC 1,738 / TABLE DATA 129다. release agent 자체에는 pg_dump/pg_restore 실행 계약이 없다.
따라서 매 execute 직전 별도 승인된 백업 단계에서 fresh custom dump를 만들고 SHA-256·byte size·TOC parse/count와
current DB identity를 증거에 결속한 뒤에만 승격한다. 기존 dump 존재만으로 이 단계를 생략하지 않는다.
release agent의 자동 rollback 범위는 exact API/Web image, Compose 적용 상태, active-state 파일뿐이다. 이미 적용된 DB migration이나 데이터 변경은 자동 복구하지 않는다. migration/data restore는 별도 owner 승인·대상 DB identity·dump hash를 갖춘 복원 절차로만 수행한다. 이미지 rollback 성공을 DB rollback 성공으로 기록하면 안 된다.
- active archive
4d15d055…119d4d, exact API/Webd5021950…e4b1/9796c092…4b36, fresh dump SHA, previous6030a677…c611보존. - health ok, db/engine true, auth 401, OpenAPI 126.
http://100.116.83.60:8088실제 평문 NAS origin에서 11-spec browser review 112/112.crypto.randomUUID예외 0, error boundary 0, SSE→DB review PASS.- 학생 홈→회기→리뷰→G4/G5 반환 연습의 desktop/mobile·DB read reload·멱등·UUID 비노출 유지.
- 앞선 실패 시 자동 rollback과 previous image 복구를 검증했고 최종 성공 뒤 G8 카드를 DONE으로 바꿨다.
6.3 G7: 외부 종료 Gate
G7 내부 소스 계약은 완료됐고 scripts/check-g7-external-proof.py는 현재 의도적으로 exit 1이다.
네 artifact를 같은 public host, 겹치는 50분 시간창으로 수집해야 한다.
운영·코드 선행조건과 무마이크 rehearsal은 닫혔다.
- 공개 API는 detached-clean
b34623f3…·tree32cc85c7…에서 OpenAPI 126과/admin/voice-runtime을 제공한다./voice/health는local_whisper/small+melotts/melotts-korean, Uvicorn WS queue 4를 정확히 보고하고 receipt9f8d1941…a21bf가 passed다. - local Whisper 9882와 MeloTTS 9883은 API보다 먼저 exact readiness를 통과했고, 런처는 기존 비정상 리스너를
임의 종료·재사용하지 않는다. public health는 promotion과 rehearsal 뒤에도
status=ok·db=true·engine=true다. - 외부 실행 전 코드 P0 3건은 완료: (1) production 프로세스 exit 0은 canonical checker
returncode == 0 && gate_closed == true에 결속한다. (2) 실제 브라우저 Originhttps://vignette.chanpaca.net은 exact HTTPS allowlist로, API/WSS/admin/topologyapi-vignette.chanpaca.net은 별도 transport host와wss/httpsscheme으로 검증한다. (3) 실제 capture 기본·최소를 3,120초로 올리고 sampler를 ceil+terminal sample로 계산하며, checker가 voice/runtime/topology 공통 교집합 3,000초 이상을 강제한다. current six-suite 87/87과 runtime sampler 4/4를 통과했다. - 현재 공개 topology는 Windows host Uvicorn + cloudflared다. 명시적
windows_hosttopology mode는 구현 완료됐다. API/cloudflared의 PID·start time·executable name/SHA256·command-line SHA256·Git SHA·cwd를 pin하고, 각 sample 전후 identity와 Git HEAD drift를 검사한다. RSS/peak RSS/CPU/handles/threads, process TCP/established/listener, API listen-port owner/conflict와 host TCP high-water도 수집한다. 기존linux_compose의 api+caddy·fresh-container 규칙은 그대로 유지한다. - Windows topology는 API/cloudflared의 PID·start·exe SHA·command SHA·cwd와 detached-clean commit/tree,
runner/collector/checker SHA,
psutil==6.1.1을 결속한다. 30초 rehearsal에서 API listener owner/conflict와 cloudflared TCP, RSS/CPU/handles/threads를 7회 수집했고 raw command line·endpoint·UUID·email은 저장하지 않았다. - watchdog·로그온 boot 두 task도 같은
b34623f3…stable root에 재등록했다. commit/tree/script hash marker, working directory,LastTaskResult=0·Ready를 확인했다. 실제 Windows 재부팅 뒤 복구 smoke는 아직 남아 있다. - 운영 Python에
psutil이 없으면 collector는command_unavailable:psutil로 fail-closed한다. currentapps/api/requirements.txt와 명시적 Python 3.11 모두psutil==6.1.1로 고정됐다. - 현재 운영 STT 모델은 이 호스트에서 실측된 CPU int8
small로 고정한다. cuDNN 9가 설치되고 별도 성능·정확도 gate를 통과하기 전까지 launcher·API runtime metadata·50분 runner/checker expected model을 모두small로 유지한다. - human voice-gain pack은 category와 categorical κ를 필수로 포함하고, preregistration이 held-out 공개보다 앞서야 하며,
양 조건이 모두 관측된 50회기/150축만 paired 표본으로 집계한다. 한쪽 결측이면 양쪽 모두 최대오류 ITT로 처리한다.
scripts/check-g7-human-voice-gain.py --input <pack.json>을 먼저 통과하지 못하면 production runner는 마이크를 열기 전에 exit 2로 끝난다. 스키마는--print-schema로 출력한다. 관련 API+script 계약은 97/97 통과했다. - 무마이크 rehearsal 산출물은
D:\workspace\vignette-runtime-evidence\g7-rehearsal-b34623f3db05-20260809T134105Z에 있다. voice61af2e98…009c, runtimec6e8670e…4454, topology89f1cb86…a251이며 세 leg 모두 passed,physical_capture=false다.
& $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 — 운영 기본값local_whisper/melotts의 ready metadata가 실제 provider/model과 정확히 일치하는 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 — held-out 30명 외 calibration split 참가자 포함 총 최소 31명 / held-out 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.
G7 Windows topology 회귀 명령:
Set-Location D:\workspace\vignette
$env:PYTHONPATH='D:\workspace\vignette\apps\api'
& $py -X utf8 -m unittest scripts/test_g7_external_proof.py scripts/test_g7_topology_evidence.py `
scripts/test_g7_runtime_evidence.py scripts/test_g7_external_voice_soak.py `
scripts/test_run_g7_external_proof_window.py apps/api/app/test_g7_voice_gain_evidence.py
& $ruff check scripts/capture-g7-topology-evidence.py scripts/check-g7-external-proof.py `
scripts/run-g7-external-proof-window.py scripts/test_g7_topology_evidence.py `
scripts/test_g7_external_proof.py scripts/test_run_g7_external_proof_window.py
사용자에게 받아야 하는 것 (코드로 대체 불가):
- 물리 장치 선택과 3,120초 실행에 대한 명시적 동의
- 실제 참가자·독립 평가자 운영 승인
합성 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 921 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 / 30
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
현재 HEAD 학생 UX·접근성 focused — 기준 20/20
2026-08-09 읽기 전용 재감사에서 check:api-types·typecheck·build와 아래 fixture-only 묶음이 desktop/mobile
20/20을 통과했다: Alliance Pulse 8, 자기주도 폐루프 2, insecure-origin UUID 6, pre/mid 자기점검 2,
시간만료 경고·종료 모달 2. 390×844·320×568 overflow 0, tab 키보드, Enter CTA, 44px 조작부,
4.5:1 대비, raw UUID/theory key 비노출, 동의 전 getUserMedia/enumerateDevices 0도 확인했다.
추가 회귀에서 새 음성 동의 modal을 실제로 수락한 뒤 위기 음성 종료까지 가는 desktop/mobile, 모바일 review filter 44px 계약을 desktop/mobile로 검증해 4/4 통과했다. dashboard의 file-origin은 spec-relative repo root로 고정했고, session-review fixture 소유권과 viewport assertion, admin live snapshot race를 교정했다. 16:49 단일 실행은 **120/120 PASS(92.0s)**였지만, 그 뒤 dashboard 변경분은 dashboard 10/10으로만 따로 통과했다. 따라서 현 트리는 110 불변 + 10 최신 분할 GREEN이고, 최종 clean source에서 아래 exact 묶음을 다시 단일 실행해 HEAD/tree/archive 결속 뒤 120/120을 요구한다.
current-source fixture exact 120 — clean HEAD 재현 명령
정확한 수집 구성은 dev-dashboard 10 + session-mvp 24 + session-review 24 + learner 14 +
session-layout 8 + admin 40 = desktop/mobile 120이다. 127.0.0.1:8000은 반드시 복구 dump에서 만든
disposable API/DB/engine 전용 스택이어야 한다. 이 묶음은 dev-login·세션 쓰기를 포함할 수 있으므로 public
8001, 복구 원본 55432, candidate DB, NAS DB에 연결하지 않는다.
$ErrorActionPreference = 'Stop'
[Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
$OutputEncoding = [Text.UTF8Encoding]::new($false)
$cleanRoot = 'D:\workspace\vignette-clean-<NEW_SHA>'
$cleanWeb = Join-Path $cleanRoot 'apps\web'
$tempRoot = Join-Path $env:TEMP 'vignette-clean-fixture-120'
$tempWeb = Join-Path $tempRoot 'apps\web'
$tempEvidence = Join-Path $tempRoot 'docs\ops\evidence'
if (Test-Path -LiteralPath $tempRoot) { throw "기존 TEMP와 충돌: $tempRoot" }
$pw = Join-Path $cleanWeb 'node_modules\.bin\playwright.cmd'
if (-not (Test-Path -LiteralPath $pw -PathType Leaf)) {
throw 'clean worktree에서 npm ci --ignore-scripts를 먼저 실행해 node_modules를 준비할 것'
}
$health = Invoke-RestMethod -Uri 'http://127.0.0.1:8000/health' -TimeoutSec 5
if ($health.status -ne 'ok' -or -not $health.db -or -not $health.engine) {
throw 'disposable API8000의 db/engine ready가 아님'
}
New-Item -ItemType Directory -Path $tempWeb -Force | Out-Null
New-Item -ItemType Directory -Path $tempEvidence -Force | Out-Null
$config = Join-Path $cleanWeb 'playwright.config.ts'
$files = @(
'e2e/dev-dashboard.spec.ts',
'e2e/session-mvp.spec.ts',
'e2e/session-review.spec.ts',
'e2e/learner.spec.ts',
'e2e/session-layout.spec.ts',
'e2e/admin.spec.ts'
)
$env:PLAYWRIGHT_PORT = '15374'
$env:VITE_API_PROXY_TARGET = 'http://127.0.0.1:8000'
Remove-Item Env:PLAYWRIGHT_BASE_URL -ErrorAction SilentlyContinue
Remove-Item Env:PLAYWRIGHT_SKIP_WEB_SERVER -ErrorAction SilentlyContinue
$code = 99
try {
Push-Location $tempWeb
try {
& $pw test @files ("--config={0}" -f $config) `
--project=chromium-desktop --project=chromium-mobile `
--workers=4 --reporter=line ("--output={0}" -f (Join-Path $tempRoot 'pw-results'))
$code = $LASTEXITCODE
} finally {
Pop-Location
}
} finally {
$listeners = @(Get-NetTCPConnection -State Listen -LocalPort 15374 -ErrorAction SilentlyContinue)
$listenerError = $null
if ($listeners.Count -ne 0) { $listenerError = "Playwright Vite listener 잔존: $($listeners.OwningProcess -join ',')" }
if (Test-Path -LiteralPath $tempRoot) {
$resolved = (Resolve-Path -LiteralPath $tempRoot).Path
if ($resolved -ne $tempRoot) { throw "unexpected TEMP target: $resolved" }
[IO.Directory]::Delete($tempRoot, $true)
}
if ($listenerError) { throw $listenerError }
}
if ($code -ne 0) { throw "clean fixture 120 failed: $code" }
과거 API-ready 없이 돌린 별도 Vite-only 혼합 실행의 session-layout 8건은 DB persona가 없어 제품 assertion 전에
seed_fallback/degraded로 중단됐다. 위 exact 120 명령은 이 사각지대를 닫기 위해 disposable API의 db/engine health를
선행 강제한다. 최종 완료 때는 session-layout 8/8과 returned-practice DB closed-loop 4/4를 모두 다시 통과시킨다.
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는 627 tests / 45 files다. 실행 환경/API/DB를 정확히 맞추지 않고 fixture failure를 제품 failure로 오인하지 않는다. 같은 blocker가 두 번 반복되면 전체 재시도 대신 원인·증거·수정 계획을 먼저 보고한다.
주기 실회기 검증은 scripts/run-periodic-learner-e2e.py가 소유한다. NAS 112건 중 실 API/DB는 22건이고
route fixture는 90건이므로 전체 숫자를 실제 회기 폐루프로 부르면 안 된다. 새 runner는 clean HEAD/tree에서만
전용 engine/DB/API/Web을 만들고 같은 학습자의 SSE→review→G4/G5 0→1→1과 returned-practice desktop/mobile 4건을
검증한 뒤 sentinel resource·PID·listener·temp 잔여 0을 영수증으로 남긴다. 공개 8001/55432/9099와 NAS는 금지다.
최초 세 full run은 SSE body replay, durable turn 전 종료, 비종료 SSE response.finished() 대기를 차례로 찾아 모두
fail-closed했다. 4차 clean run은 reload 뒤 persisted episode를 학습자만 실행으로 표시하는 제품 결함을 검출했다.
5·6차 clean run은 핵심 동일 학습자 SSE·review·G4/G5 0→1→1을 연속 통과했다. 5차는 fixture 공유, 6차는 desktop이 적재한 성공 상태를 mobile G4 테스트가 최초 버튼으로 찾은 locator 결함 때문에 전체 receipt가 FAILED다. 여섯 run 모두 public/NAS/active DB 접촉 0, cleanup GREEN이다. runner는 핵심 폐루프와 후속 회귀에 별도 zero-state fixture를 준비하고(unit 12/12), mobile은 성공 상태의 반영 상태 다시 확인을 누른다. 리뷰의 요약·강점·개선점·다음 문장·내담자 반응·워크시트 근거 literal privacy 토큰도 표시층에서 자연어화했다. 아직 전체 receipt GREEN은 없으므로 자동 full runner 등록은 금지다.
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.py·scripts/run-g7-external-proof-window.py와 대응 G7 테스트 3개apps/web/src/lib/uuid.ts·apps/web/e2e/insecure-context-uuid.spec.ts(신규, 비-secure origin 결함 수정)apps/api/app/session_read_model.py·apps/api/app/services/session_metrics.py·apps/api/app/test_session_read_model.py·apps/web/e2e/session-persistence.spec.ts(UTC/KST P0와 실DB browser 회귀)apps/web/index.html·apps/web/e2e/self-directed-learning-loop.spec.ts(SPA route 전환 중 boot diagnostic 회복)apps/web/e2e/admin.spec.ts·apps/web/e2e/dev-dashboard.spec.ts·apps/web/e2e/session-review-fixture.ts·apps/web/e2e/session-review.spec.tsscripts/watch-public-runtime.ps1·scripts/boot-public-runtime.ps1·scripts/install-public-runtime-task.ps1·scripts/register-boot-task.ps1·scripts/watch-public-runtime-hidden.vbs·scripts/test_public_runtime_watchdog_provenance.pyapps/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 증거가 우선이다.
11. 권장 멀티에이전트 실행 구조
사람 개발자 작업계획은 만들지 않는다. 구현·테스트·배포·증거 동기화는 에이전트가 수행하고, 사람에게는 코드로 대체 불가능한 동의·인증 세션·실제 참가자/평가자 승인만 요청한다. 동시 작업은 최대 세 하위 lane으로 나누되, NAS/public/DB mutation owner는 항상 한 에이전트만 둔다.
- G7 proof lane: 완료된 clean public runtime과 무마이크 rehearsal을 기준선으로 보존하고, 장치 선택·명시 동의 뒤 3,120초 mic/runtime/topology 동시 캡처와 human pack·canonical checker를 닫는다.
- G8/NAS monitor lane: 완료된 archive·images·health·자동화 drift를 읽기 전용으로 감시. 새 material milestone 전 mutation 0.
- E2E/SSOT lane: local fixture, disposable DB browser, visual/accessibility, dashboard/TODO/backlog 정합을 읽기 전용으로 감사.
루트 에이전트는 각 lane의 증거 SHA와 실패 원인을 합쳐 게이트를 판정한다. 같은 blocker가 두 번 반복되면 세 번째 재시도 전에 원인·증거·수정 계획을 사용자에게 보고한다.
12. 새 에이전트에 그대로 줄 시작 프롬프트
D:\workspace\vignette의 docs/HANDOFF.md를 인수인계 SSOT로 읽고, AGENTS.md → docs/README.md →
docs/dev_dashboard.html → docs/TODO.md → docs/ops/backlog-2026-06-26.md 순서로 현재 상태를 재확인해.
G8 배포 source는 HEAD `61a41d1f…6af`·tree `87dec55d…3b77`·archive `4d15d055…119d4d`다. 격리 NAS의
API/Web `d5021950…e4b1`/`9796c092…4b36`, candidate 112/112, actual NAS-origin 112/112, health·auth 401·OpenAPI
126을 통과했고 fresh dump `36ec8748…24db8`과 previous `6030a677…c611`을 보존했다. G8은 DONE이며 새 material
milestone 전에는 NAS를 mutation하지 마. 현재 공유 worktree의 후속 dirty 변경은 이 배포 source와 분리해서 다뤄.
최종 목표는 G0~G8 아홉 목표를 코드-only가 아니라 실제 runtime 증거로 모두 닫는 것이다. G0~G6과 G8은 DONE이다.
G7은 내부 구현과 clean public runtime·local_whisper/melotts ready·authenticated public WSS 무마이크 rehearsal까지 완료했다.
이제 사용자가 고른 장치와 명시 동의를 받은 물리 마이크 3,120초 soak, 같은 시간창의 runtime/topology high-water,
독립 human voice-gain pack을 모아 canonical checker exit 0을 만들어야 한다.
G7 runner exit의 checker exit 0/gate_closed 결속, browser Origin과 API/WSS host 분리, 3,120초 capture와
3,000초 공통 overlap, detached-clean HEAD/tree, runner/collector/checker SHA와 exact psutil pin은 소스에서 완료됐다.
API/cloudflared는 detached-clean `b34623f3…`·tree `32cc85c7…`에서 새 PID로 교체됐고 safe receipt
`9f8d1941…a21bf`가 passed다. 공개 OpenAPI 126·`/admin/voice-runtime`·local provider/model·queue 4와
public health db/engine true를 확인했다. 30초 rehearsal은 인증 WSS·runtime·Windows topology 세 leg 7 samples를
모두 통과했고 `physical_capture=false`·UUID/email 0이다. 이 증거를 물리 마이크나 사람 평가로 과장하지 마.
watchdog·로그온 boot도 같은 stable root와 exact Git commit/tree/script SHA에 재등록했고 두 task의 명시 실행 결과가
0·Ready다. 실제 Windows 재부팅 뒤 자동복구 smoke는 별도 운영 게이트로 남는다. cwd/Git pin이나 기존 Linux Compose
gate를 약화하지 말고 합성 증거로 G7을 DONE 처리하지 마.
학생 자기주도 폐루프는 홈 추천→회기→리뷰→G4 처방/G5 전이→별도 재연습→completed-session observer→
actual attempt/execution→read-model reload→before/after·멱등까지 desktop/mobile 실제 DB/browser로 검증해.
최종 검증은 API full, gateway full, schema/provenance, Web API types/typecheck/build, fixture E2E, disposable DB E2E,
NAS-origin E2E, visual/accessibility, auth/OpenAPI/assets/SSE→DB review를 포함해야 한다.
독립 작업은 서브에이전트로 병렬화하되 NAS/public/DB mutation은 단일 owner만 수행해. 공개 DB 복구 volume과
pre-recovery container/dumps, NAS named volume과 previous images는 삭제하지 마. 비밀/PII/raw UUID를 증거에 남기지 마.
과거 로컬 recovery dump는 현재 위치를 별도로 확인하기 전 삭제로 단정하거나 그 파일을 전제로 복원하지 마.
2026-08-09 fresh public dump `vignette-dev-db-vignette-20260809-122235Z.dump` SHA `f1fd569c…5f64`도 보존해.
source-only PASS나 health-only를 DONE으로 부르지 말고, 대시보드·TODO·backlog·HANDOFF를 마지막 증거와 함께 동기화해.
모든 게이트가 실제로 닫히기 전에는 goal complete를 선언하지 마.