vignette/docs/archive/project-cleanup-2026-09-07/HANDOFF-before-cleanup.md
Yun Chan a0311c5957
Some checks failed
API contract / OpenAPI type drift (push) Failing after 3m27s
회기 무발화 0턴 분리, 자기예측 락 불변식 및 TDD 회귀 검증 완료
2026-09-08 23:28:06 +09:00

68 KiB
Raw Blame History

Vignette G0~G8 완료 추진 핸드오프

Updated: 2026-08-18 KST (엔진 readiness 장애 수정·재승격 · 유스케이스 TDD 239 · 공개 Web 배포) Workspace: D:\workspace\vignette 이 문서는 다음 에이전트가 G0~G8 아홉 목표 중 마지막 G7 외부 게이트를 닫기 위한 실행 기준이다.

0. 현재 상태 한 줄 요약

G0~G6의 내부 구현·증거와 G8 clean-head 배포·rollback 증거는 종료됐다. G8은 source HEAD 61a41d1f…6af·tree 87dec55d…3b77·archive 4d15d055…119d4d로 candidate 112/112와 actual NAS-origin 112/112를 모두 통과했다. 다만 2026-08-12 current NAS는 고정 LAN engine relay 상실로 DEGRADED이며 15:24 KST health 요청도 3회 타임아웃이라, 과거 증거와 현재 runtime GREEN을 분리한다. 공개 앱은 health ok·db/engine true· OpenAPI 126·auth 401·local voice exact를 유지한다. 앞선 fail-closed·verified rollback 이력과 previous 6030a677…c611은 보존했다. G7 공개 런타임은 detached-clean a73bcd24…·tree b02b3a1b…로 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 a73bcd24…·OpenAPI 126·local voice, no-mic rehearsal 3 legs × 7 samples
G8 Continuous Improvement 증거 DONE · CURRENT NAS DEGRADED 실제 image rollback 2회 + executed receipt 2건, source/archive 결속, candidate/origin 112/112; current engine relay 복구·재검증 대기

대시보드 status count는 33 done / 2 doing / 0 planned다.

G8 배포 source 기준선은 branch master, HEAD 61a41d1f08239b5f9e99cfae42a206be250416af·tree 87dec55daf0a22ca5c2e489cd803a2f731e63b77이며 2회 동일 archive SHA는 4d15d055d74f33f1fa2ff470afbf900d565ab238b19c79df34ca7ba240119d4d다. 공유 worktree의 후속 변경은 이 배포 source에 포함되지 않으므로 G8 완료 증거와 섞지 않는다.

2026-08-18 세션 — 엔진 장애 수정·유스케이스 TDD·재승격

  • 공개 엔진 3.5시간 장애(06:30~10:05 KST): reasoning_effort=high 콜드 프로브가 20초를 넘기면 실패가 성공과 같은 TTL(운영 1800초)로 캐시돼 공개 API가 engine=false로 오염됐고, 사용량 한도 소진 구간에는 Reached maximum budget ($0.5)로 이어졌다. 워치독은 기본 /ready의 캐시 200만 보고 API만 재시작하는 루프에 빠졌다. 수정: ENGINE_READY_FAILURE_TTL_SECONDS(기본 30초) 분리 + ENGINE_READY_TIMEOUT_SECONDS 20→45초, 회귀 4건(engine_gateway/test_ready_cache.py). gateway 62/62 · backend 986 passed · ruff clean.
  • 운영 경계 기록: 공개 엔진 claude -p는 소유자 Claude 구독을 개발 에이전트 작업과 공유한다. 대량 병렬 에이전트 실행은 공개 서비스의 생성 경로를 굶길 수 있다(이번 장애의 2차 원인).
  • 공개 런타임 재승격: detached-clean 4771b97c…c880·tree 286723de…c1fa 루트로 API/게이트웨이/ cloudflared를 교체하고 워치독·부트 task를 같은 루트에 SHA pin 재등록, 명시 실행 LastTaskResult=0. 1차 승격은 새 worktree에 gitignored apps/api/.env가 없어 fail-closed로 실패했다(약 4분 다운타임). 다음 승격부터는 시작 전에 이전 루트의 apps/api/.env를 새 루트로 복사하라.
  • 유스케이스 TDD 캠페인: 16테마 uc-*.spec.ts 239 시나리오(수집 1109 tests/61 files)를 RED 우선으로 작성했다. 제품 결함 수정 4묶음(설정 지원 티켓 작성 UI 부재, 관리자 해결 노트 입력 부재, 포커스 링 가림, 라벨/헤딩/대비/44px/오버플로 결함)과 기존 스펙 드리프트 교정을 닫았다. 증거: docs/ops/evidence/usecase-tdd-2026-08-18.json.
  • 직렬 게이트(e2e:single-run) 후속: 11:10 KST 실행은 52 passed/5 failed/4 skipped. 삼각화 결과 breakpoint-sweep 실패는 widthsFor 솎아내기가 실기기 대표 폭을 탈락시키는 테스트 결함(교정 후 passed), kb-source-packs 500은 수정 커밋 이전에 기동한 낡은 dev API(:8000) 프로세스가 원인(재기동 후 passed), voice-success·session-persistence 실패는 Claude 구독 세션 한도(resets 3pm)로 엔진 502가 난 환경 결함. 교사 재평가 2건은 엔진 가용 게이트가 skip 처리하며 15:00 KST 리셋 후 재검증이 열려 있다.
  • 공개 Web Cloudflare Pages 배포: 소유자 지시로 detached-clean cf899b7f…7c96 후보 (D:\workspace\vignette-public-web-cf899b7f)를 빌드×2 결정성(599파일 sha256 전일치) 확인 뒤 production 4edc02d3로 배포했다. 7개 origin(커스텀 도메인+직전 6세대)에서 143자산을 보존해 최종 742파일, 커스텀 도메인은 새 엔트리 index-BWxBaCPM.js/index-BUfQc2Rm.css를 서빙하고 직전 엔트리 자산도 200으로 살아 있다. 증거: docs/ops/evidence/public-web-deploy-2026-08-18.json.
  • NAS 엔진 릴레이 복구(1단계 종료): start-nas-preview-engine.ps1 -CheckOnly 프리플라이트가 4종 SHA pin + LAN 192.168.0.223:9100 바인딩으로 통과했다(mutation=false, 영수증 D:\workspace\nas-relay-state\receipt-checkonly-20260818.json). env는 NAS preview API 실행 컨테이너의 ENGINE_GATEWAY_SHARED_SECRET을 값 노출 없이 로컬로 반출해 결속했다. 실제 1회 시작은 엔진 한도 리셋(15:00 KST) 뒤 15:05 자동화가 같은 핀으로 수행하고 NAS health 3회·auth 401·OpenAPI 126을 재검증한다. 증거: docs/ops/evidence/nas-relay-recovery-2026-08-18.json.
  • agy 공급자 복구(2026-08-18 오후): 관리자 AI 운영에서 agy 전환이 막힌 이중 결함을 고쳤다 — ① agy modelsid<TAB>라벨 형식으로 바뀐 것을 파서가 못 읽음(5afd92f8), ② 승격 체인의 psutil 환경 이식에서 SystemRoot 유실로 Go 계열 CLI가 빈 목록을 반환(6369f294 — _cli_subprocess_env 백필 + 런처 Machine 스코프 병합). 공개 런타임을 6369f294로 승격하고 워치독·부트 태스크를 모두 새 루트에 재핀했다(워치독은 install-public-runtime-task.ps1, 부트는 register-boot-task.ps1 — 승격 시 둘 다 실행해야 구 루트 되살림 경합이 없다). 검증: capabilities 14모델 + 실생 턴 gemini-3.6-flash-high 응답·비용 계약 통과. 증거: docs/ops/evidence/agy-provider-recovery-2026-08-18.json.
  • 회기 종료 503 핫픽스(bf5f7352): end_session의 pinned_fact upsert가 learner 역할 연결로 실행돼 RLS에 거부되던 잠복 결함 — pinned_fact의 SELECT/UPDATE 정책은 AI 뷰·admin 전용이다. 같은 learner uid로 acquire(ai_view="evaluator")를 열어 저장을 분리했고, 공개 DB 롤백 재현(learner 거부·AI 뷰 통과)으로 실증했다. 엔진 건강 시 deep 평가가 팩트를 반환하는 순간부터 발화하므로, degraded 구간 E2E가 이 경로를 덮지 못한 이유도 같다. 소유자 실세션 종료 성공과 로컬 실API E2E 2회(종료 200 + pinned_fact 실적재 실증)로 운영 검증했다.
  • 2026-08-18 심야 연쇄 복구·NAS current GREEN: 대청소 사건(node_modules·구 런타임 워크트리·Temp 소실)으로 릴레이가 죽고, Docker Desktop 사망·whisper 9882 WinNAT 포트 예약·DHCP의 LAN IP 회수 (223→224)가 겹쳤다. 릴레이를 현행 루트에 같은 핀으로 재기동하고 224로 계약 이전(방화벽 재발행·NAS .env 재구성), 클로드 예산 한도 재발으로 NAS를 agy_cli로 전환해 health 3/3 engine true·auth 401· OpenAPI 126·actual-origin turn→review smoke를 모두 통과 — 201차 current GREEN 달성. 권고 3건은 docs/ops/evidence/nas-relay-recovery-2026-08-18.json 참조.

이 문서가 인계된 정확한 중단점

  • 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를 호출하지 않는다.
  • current NAS 복구용 scripts/start-nas-preview-engine.ps1은 source-pinned fail-closed 계약으로 보강했다. detached-clean HEAD/tree, launcher·Python·env SHA, exact consumer URL, explicit LAN 승인, 외부 state/receipt, listener owner와 generation readiness를 강제하고 loopback contract 8/8을 통과했다. 2026-08-12에는 detached-clean f08e03de…f191·tree 00275133…0abd에서 exact -CheckOnly도 PASS했다. launcher/Python/env SHA는 492d8b5c…b27c/5f7b89a6…52ec/e53acedf…8a39, mutation=false, listener 0→0, runtime state 생성 0이다. 실제 9100 listener, NAS 설정·컨테이너·DB는 건드리지 않았다. 다음 mutation은 소유자 승인 뒤 단일 owner가 actual 1회→NAS health/auth/OpenAPI/assets/실회기 재검증 순서로만 수행한다.
  • 최신 full disposable GREEN은 공통 톱바 44px 계약을 포함한 clean b02bee26…·tree 8f677bba…periodic-learner-e2e-20260812-171547.json·SHA-256 8c11a136…af8e3이다. 487.3초에 동일 학습자 브라우저 회기 생성·SSE·review ready·G4/G5 0→1→1, 별도 returned desktop/mobile 4/4와 route mock 0을 통과했고 public/NAS/active DB 접촉 0, container·volume·network·listener·temp 잔여 0을 독립 재확인했다.
  • 공개 Web 보정은 detached-clean 00f851a6…c229·tree 9f78cd16…9486 후보로 배포 직전까지 고정했다. API generated type LF 계약, npm ci --ignore-scripts, API type contract, typecheck, 2회 byte-identical production build와 상단 44px·로그인 무가로넘침·자기주도 폐루프 focused E2E 12/12를 통과했다. 새 primary CSS/JS는 index-Dzmt2kJS.css SHA da5e8535…fb9aindex-BUoTLplD.js SHA 08654d08…3c05다. 현재 공개와 최근 Pages 5개 origin의 지연 로드 자산 133개를 더해 최종 732파일로 만들었고 현행 공개 CSS/JS SHA도 exact 보존했다. actual 공개는 health ok·db/engine true지만 구 Web이라 모바일 상단 6개 조작면이 44px 미만이고, NAS는 health timeout·9100 listener 0·로그인 overflow 10px다. 공개/NAS 배포는 0이며 기계 판독 증거는 docs/ops/evidence/public-web-preflight-2026-08-12.json이다.
  • 앞선 두 DB init 실패는 core.autocrlf=true detached checkout이 99_app_role.sh를 CRLF로 만든 것이었고, .gitattributes*.sh text eol=lf 뒤 새 worktree에서 tracked shell CRLF 0·해당 파일 CR 0과 실제 DB healthy를 확인해 닫았다. 공통 톱바의 브랜드·공간 전환·테마·로그아웃은 desktop/mobile 모두 44px이고, auth overflow와 함께 source/full E2E GREEN이지만 아직 공개/NAS에는 배포하지 않았다.
  • 마지막 완료 작업은 G8 source HEAD 61a41d1f…6af·tree 87dec55d…3b77·archive 4d15d055…119d4d의 candidate 112/112와 actual NAS-origin 112/112다. API/Web d5021950…e4b1/9796c092…4b36, health ok·db/engine true·auth 401·OpenAPI 126, fresh dump 36ec8748…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 a73bcd24…·tree b02b3a1b…의 receipt 47388d58…9b08가 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를 같은 a73bcd24… 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 종료 증거는 충족. current NAS runtime 복구와 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 종료 도구 104/104(runtime/checker 6파일 94 + intake 6 + standalone validator 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. 작업 시작 전 반드시 읽을 파일

  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에서 먼저 확장되는 것을 막는다.)
  • 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.pydocs/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…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에 완료됐다. 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 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
  • 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 ONLYROLLBACK했고 PII를 출력하지 않았다.
  • health environment=prod, db=true, engine=true, engine_mode=claude_cli, dev-login disabled, unauth /auth/me 401
  • 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/healthlocal_whisper/small·melotts/melotts-korean·WS queue 4를 보고한다.
  • DB 컨테이너는 vignette_recovered_prod_20260807 named volume과 unless-stopped를 유지한다. API/cloudflared는 detached-clean a73bcd24…·tree b02b3a1b…에서 실행되고 receipt 47388d58…9b08가 passed다. watchdog·로그온 task도 같은 stable root/commit/tree/script SHA에 pin해 명시 실행 결과 0·Ready를 확인했다. 실제 Windows 재부팅 뒤 자동복구 smoke만 남아 있다.
  • 2026-08-12 public reconnect incident: 휴면 Gradle daemon이 127.0.0.1:8001을 점유했고 cloudflared와 9882/9883 sidecar가 없는 상태에서 PowerShell 5.1 native stderr가 start/watch/boot의 복구 분기를 조기 종료했다. exact Gradle PID만 중지하고 DB dump 9cbda31e…0ce5(7,532,625 bytes·TOC 1,492)를 먼저 고정했다. 복구 코드는 103/103과 PS5.1 parser를 통과했고, detached-clean a73bcd24…·tree b02b3a1b…에서 API/cloudflared를 fresh 교체했다. receipt 47388d58…9b08 passed, public health 3/3·db/engine true·OpenAPI 126·local voice exact, 웹 진단 overlay/console error/[NAME] 0, watchdog·로그온 boot 명시 실행 result 0을 확인했다.

보안상 과거 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 · project vignette-preview-20260807 · remote root /volume1/docker/vignette-preview-20260807.
  • source HEAD 61a41d1f08239b5f9e99cfae42a206be250416af · tree 87dec55daf0a22ca5c2e489cd803a2f731e63b77 · 2회 동일 active archive 4d15d055d74f33f1fa2ff470afbf900d565ab238b19c79df34ca7ba240119d4d.
  • API sha256:d502195021beafe869c555f3ec80269426062ef4586dc15a3e049f9c8660e4b1 / Web sha256:9796c0925f6a984cb846b61a4f13fda4d403bda7b3a2b6232b728cebbcdc4b36 exact 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, SHA256 36ec8748cf28c520d9489d108dee9d4a154e6260f6996b4da3e0fe3b8a624db8, 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.tsrandomUuid()로 통일. fallback도 crypto.getRandomValues를 우선 사용해 idempotency key의 예측 불가능성을 유지하고, Web Crypto가 아예 없을 때만 Math.random으로 내려간다.
  • 회귀: apps/web/e2e/insecure-context-uuid.spec.ts 6/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 계약은 다음과 같다.

  1. tracked dirty가 있으면 fail-closed. untracked 로그·테스트 결과는 archive에서 제외한다.
  2. exact Git HEAD, tree, git archive SHA를 증거에 기록한다.
  3. 같은 HEAD의 archive를 두 번 만들어 SHA가 동일한지 검증한다.
  4. candidate는 그 archive만 풀어 만들고 patch/manifest를 암묵 적용하지 않는다.
  5. 기존 preflight·API full·Web type/build·release E2E·DB snapshot·rollback 절차는 그대로 유지한다.
  6. 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개 회기 스펙만 실행한다.
  7. 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, active 6030a677…c611, desired archive 6b1b15bd…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, tree e4f15001…308b, 2회 동일 archive 1109bf86…0f4b(482,662,400 bytes)를 detached clean worktree에서 고정했다.
  • 새 milestone과 dry-run은 active 6030a677…c611 → desired 1109bf86…0f4b, deployment_mutated=false로 통과했다. 실행 직전 NAS custom dump 6f4b95a7…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, tree 5c5f12a8…524e, 2회 동일 archive 20d49694…fa1a execute는 candidate 112/112를 통과하고 NAS 새 API/Web image d5021950…/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를 재검증해 rollback verified로 종료했다.
  • focused exact-image 평문 origin에서 요청이 API까지 가지 않고 일반 오류로 끝나는 것을 재현했다. 원인은 문장 원문을 보내지 않기 위한 fingerprint가 crypto.subtle.digest에만 의존해 insecure origin에서 예외가 난 것이다. 원문 외부 전송 없이 portable SHA-256 fallback을 추가하고 표준 digest c06db0f0…62aa를 payload에서 exact 검증했다. localhost secure origin 2/2와 실제 Tailnet insecure origin 2/2가 같은 digest로 통과했다. 이 수정의 새 clean commit만 다시 결속해 전체 execute를 재개한다.

P0 수정 완료 — 회기 timestamp UTC 전송과 KST 표시 날짜

  • 최초 실패 스펙: session-persistence.spec.tspersists 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/Web d5021950…e4b1/9796c092…4b36, fresh dump SHA, previous 6030a677…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 a73bcd24…·tree b02b3a1b…에서 OpenAPI 126과 /admin/voice-runtime을 제공한다. /voice/healthlocal_whisper/small + melotts/melotts-korean, Uvicorn WS queue 4를 정확히 보고하고 receipt 47388d58…9b08가 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) 실제 브라우저 Origin https://vignette.chanpaca.net은 exact HTTPS allowlist로, API/WSS/admin/topology api-vignette.chanpaca.net은 별도 transport host와 wss/https scheme으로 검증한다. (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_host topology 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도 같은 a73bcd24… stable root에 재등록했다. commit/tree/script hash marker, working directory, LastTaskResult=0·Ready를 확인했다. 실제 Windows 재부팅 뒤 복구 smoke는 아직 남아 있다.
  • 운영 Python에 psutil이 없으면 collector는 command_unavailable:psutil로 fail-closed한다. current apps/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로 출력한다.
  • 실제 평가팀 intake는 scripts/prepare-g7-human-voice-gain-intake.py --create-template <dir>로 시작한다. 생성물은 header-only이고 template_only=true라 증거가 아니다. provenance와 비식별 participant/labeler/observation CSV를 채운 뒤 --compile <dir> --out <pack.json>을 실행하면 외부 report의 ICC·κ와 행 재계산값의 일치를 확인하고 production gate가 전부 통과할 때만 기존 파일을 덮어쓰지 않고 최종 pack을 만든다. 콘솔은 경로·키·라벨·잘못된 셀 값을 반사하지 않는다. runtime/checker 6파일 94건, intake 6건, standalone validator 4건을 합쳐 관련 계약 104/104를 통과했다. 빈 템플릿 행동 검증은 template_is_evidence=false, compile exit 1, pack_written=false를 반환했다. 실제 사람 데이터는 아직 없다.
  • 무마이크 rehearsal 산출물은 D:\workspace\vignette-runtime-evidence\g7-rehearsal-b34623f3db05-20260809T134105Z에 있다. voice 61af2e98…009c, runtime c6e8670e…4454, topology 89f1cb86…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>
  1. scripts/soak-public-voice-websocket.py v4 — 운영 기본값 local_whisper/melotts의 ready metadata가 실제 provider/model과 정확히 일치하는 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 — 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 재실행 절차(그대로 재현 가능):

  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 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.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는 629 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을 연속 통과했지만 fixture 공유와 mobile success-state locator를 각각 fail-closed했다. 7차 clean aa81af29…는 첫 전체 GREEN을 만들었다. 94666192…f97e7fad… run은 disposable DB init에서 멈췄고 두 번째 receipt의 cleanup 전 로그가 99_app_role.sh#!/usr/bin/env bash\r exit 127을 확정했다. Git blob LF를 Windows checkout에서도 보존하도록 .gitattributes*.sh text eol=lf를 고정했다. 공통 톱바 desktop/mobile 44px을 포함한 clean b02bee26…·tree 8f677bba…의 최신 single full run은 487.3초에 GREEN이었다. receipt periodic-learner-e2e-20260812-171547.json·SHA-256 8c11a136…af8e3은 same-learner SSE·review·G4/G5 0→1→1, returned desktop/mobile 4/4, route mock 0, public/NAS/active DB 접촉 0과 exact cleanup 0을 증명한다. heartbeat는 최신 GREEN이 6시간 이상 오래됐거나 material milestone이 바뀔 때만 다시 실행한다.

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 · 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.ts
  • scripts/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.py
  • 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 증거가 우선이다.

11. 권장 멀티에이전트 실행 구조

사람 개발자 작업계획은 만들지 않는다. 구현·테스트·배포·증거 동기화는 에이전트가 수행하고, 사람에게는 코드로 대체 불가능한 동의·인증 세션·실제 참가자/평가자 승인만 요청한다. 동시 작업은 최대 세 하위 lane으로 나누되, NAS/public/DB mutation owner는 항상 한 에이전트만 둔다.

  1. G7 proof lane: 완료된 clean public runtime과 무마이크 rehearsal을 기준선으로 보존하고, 장치 선택·명시 동의 뒤 3,120초 mic/runtime/topology 동시 캡처와 human pack·canonical checker를 닫는다.
  2. G8/NAS recovery lane: 완료된 archive·images·rollback 증거는 보존한다. current relay는 source-pinned launcher 8/8과 detached-clean f08e03de…f191 exact check-only까지 완료됐고 실제 9100은 0 listener다. 소유자 승인 전 mutation 0; 승인 뒤 같은 source/tree·hash 결속으로 actual 1회→NAS-origin 재검증.
  3. 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`을 보존했다. 이 종료 증거는 DONE이지만
current NAS는 engine relay 부재로 DEGRADED이고 2026-08-12 15:24 health 3회가 타임아웃했다. 실제 relay 복구는
소유자 승인 전 금지한다. 현재 공유 worktree의 후속 변경은 이 배포 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 `a73bcd24…`·tree `b02b3a1b…`에서 새 PID로 교체됐고 safe receipt
`47388d58…9b08`가 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를 선언하지 마.