diff --git a/apps/web/e2e/self-directed-learning-loop.spec.ts b/apps/web/e2e/self-directed-learning-loop.spec.ts
index e9962b3..f70e6a8 100644
--- a/apps/web/e2e/self-directed-learning-loop.spec.ts
+++ b/apps/web/e2e/self-directed-learning-loop.spec.ts
@@ -905,7 +905,10 @@ test.describe("학습자 자기주도 전체 루프 — 실제 src UI / route fi
});
test("React 부트가 실제로 비어 있으면 진단을 유지한다", async ({ page }) => {
- await page.route("**/src/main.tsx", (route) => route.abort("failed"));
+ await page.route(
+ /\/(?:src\/main\.tsx|assets\/index-[^/]+\.js)(?:\?.*)?$/,
+ (route) => route.abort("failed"),
+ );
await page.goto("/learn", { waitUntil: "domcontentloaded" });
const diagnostic = page.locator("#vignette-boot-diagnostic");
diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md
index 0f80267..a100466 100644
--- a/docs/HANDOFF.md
+++ b/docs/HANDOFF.md
@@ -366,6 +366,21 @@ apps/web/test-results/g7-voice-consent-mobile.png
`/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를
+ 다시 결속한 뒤 한 번만 재실행한다.
+
#### P0 수정 완료 — 회기 timestamp UTC 전송과 KST 표시 날짜
- 최초 실패 스펙: `session-persistence.spec.ts`의 `persists AI tutor coaching history through reload @single-run`.
@@ -381,8 +396,8 @@ apps/web/test-results/g7-voice-consent-mobile.png
- 고유 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 HEAD `94c681d…7fb`에는 이 수정과
- controller 변경이 없으므로 사용자에게 commit 승인을 받은 뒤 새 clean commit의 HEAD/tree/archive를 다시 산출한다.
+- **테스트 강제 클릭이나 모달 닫기 우회는 사용하지 않았다.** 첫 clean source `5221f79e…1c69`에는 제품 수정과
+ controller 변경이 포함됐고, production entry 차단 회귀 2건의 테스트 수정만 후속 clean commit으로 다시 고정한다.
### 6.2 G8: NAS preview 배포와 실제 origin E2E
diff --git a/docs/TODO.md b/docs/TODO.md
index 8806eb9..f968f08 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -500,7 +500,10 @@
- [ ] **G8 NAS-origin 재검증** — `randomUuid()` 수정과 insecure-origin 6/6 회귀를 포함한 current HEAD를
위 clean-head mode로 격리 NAS preview에 배포한다. `started_at`의 9시간 오차 P0는 UTC `+00:00`, KST 달력 날짜,
KST browser focused 실DB E2E로 수정했고 API 전체 921 passed와 코칭 이력 1/1을 통과했다. 시간종료 모달 강제
- 닫기 우회는 없다. 사용자 승인 아래 새 clean commit·HEAD/tree/archive를 결속해 실제
+ 닫기 우회는 없다. 첫 clean commit `5221f79e` execute는 API/types/build와 candidate DB-backed 회기 후반까지
+ 통과했지만 Vite 전용 `/src/main.tsx` 차단 테스트가 production hashed entry를 차단하지 못해 110/2에서
+ mutation 없이 중단됐다. source와 production preview에서 수정 회귀 2/2씩 통과했다. 이 테스트 수정의 새 clean
+ commit·HEAD/tree/archive를 결속해 실제
`http://100.116.83.60:8088` origin에서 전체 회기
E2E 0 failure·SSE→DB review·student returned-practice desktop/mobile을 다시 검증하기 전에는 G8 전체를 DONE으로
올리지 않는다.
diff --git a/docs/dev_dashboard.html b/docs/dev_dashboard.html
index a7a53a6..cf165cf 100644
--- a/docs/dev_dashboard.html
+++ b/docs/dev_dashboard.html
@@ -655,6 +655,7 @@
187차 적용(2026-08-09): G8 clean-head dry-run은 active 6030a677…c611→desired archive 6b1b15bd…a3f7를 mutation 0으로 결속했다. execute 1은 archive CRLF/API type byte-check에서, execute 2는 API·types·build 통과 뒤 session E2E 109 passed / 5 failed에서 모두 승격 전에 fail-closed되어 NAS active/image/DB는 바뀌지 않았다. source-only UUID 4건은 production 번들에서 /src/lib/uuid.ts import가 불가능한 gate 배치 오류라 별도 Vite gate 6/6으로 분리했다. 남은 실제 제품 blocker는 timezone 없는 started_at을 KST 브라우저가 약 9시간 경과로 오인해 시간종료 모달이 정상 코칭 이력을 가리는 문제다. backend UTC offset/Z 계약과 focused disposable DB/browser 회귀를 닫고 새 clean commit을 만든 뒤에만 NAS 배포를 재개한다.
188차 적용(2026-08-09): 회기 시간 P0를 우회 없이 수정했다. browser-facing started_at/ended_at과 learner/teacher dashboard timestamp는 UTC +00:00을 반환하고, 리뷰의 달력 날짜는 서버 locale 대신 KST(+09:00)를 사용한다. backend 관련 65 passed, 후속 voice 계약을 포함한 API 전체 921 passed, Ruff·compile·web typecheck를 통과했다. 고유 Compose DB/API/Web/engine과 Asia/Seoul browser에서 코칭 이력 focused 1/1은 active/null·UTC suffix·10분 미만 경과·timebar/dialog 0·reload 후 coach mark 실제 클릭과 source-pack dialog를 확인했다. 종료 뒤 container·volume·listener 0이다. 이제 새 clean commit 결속과 NAS-origin 전체 E2E가 남았다.
189차 적용(2026-08-09): G7 외부 실행 전 P0를 소스에서 닫았다. production runner exit 0은 canonical checker 0·gate_closed=true에 결속하고, browser Origin allowlist를 API/WSS/admin/topology transport host·scheme과 분리했다. capture는 최소 3,120초·ceil+terminal sample이며 checker는 voice/runtime/topology 공통 교집합 3,000초를 강제한다. Windows 증거는 detached-clean commit/tree, runner/collector/checker SHA, psutil==6.1.1을 매 sample 전후 검증한다. fresh public launcher는 mutation 전에 source·Python·cloudflared·config SHA를 고정하고, legacy API와 exact-config cloudflared를 bounded 교체해 새 PID/start/exe·command SHA/실제 cwd의 raw command-line 없는 receipt를 남긴다. receipt destination은 runtime mutation 전에 create/flush/atomic-replace 권한을 검사하고 기존 receipt를 보존한 채 원자 게시한다. 검증: G7 runner/checker/topology 86/86, launcher/sidecar 80/80, 통합 166/166, API 921, gateway 58, Web api-types·typecheck·build, 자기주도 desktop/mobile 6/6, Ruff·compile·PS5.1 parser·diff-check PASS. 실제 public 실행·마이크·사람 평가는 하지 않았으므로 G7은 external GATE다.
+
190차 적용(2026-08-09): exact include 52개를 clean commit 5221f79e…1c69으로 고정하고 tree e4f15001…308b, 2회 동일 archive 1109bf86…0f4b를 결속했다. fresh NAS dump 6f4b95a7…b529f(1,015,222 bytes·TOC 1,752/TABLE DATA 129) 뒤 execute는 API 921·types·typecheck·build·insecure-context 6/6·candidate DB 회기 후반까지 통과했지만 110/112에서 승격 전에 fail-closed했다. 실패 2건은 Vite entry /src/main.tsx만 차단하던 boot 진단 테스트가 production hashed entry /assets/index-*.js를 차단하지 못한 환경 계약 누락이었다. 두 entry를 함께 차단하도록 고쳐 Vite source 2/2와 production preview 2/2를 통과했다. NAS mutation·rollback·DB migration은 0이며, 테스트 수정의 새 clean commit을 재결속한 한 번의 execute가 남았다.
래스터만 사용이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.
기능 우선메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.
@@ -913,7 +914,7 @@
-
agentic worker + 운영 환류 + human gate
승인 source의 실제 hash·PII를 engine 호출 전에 검사하고 structured generator 1개, 결과를 공유하지 않는 red-team 2개, variant generator와 variant별 독립 judge 3개 이상을 실행한다. 정답 누수·PII·unknown source claim·open finding·안전/보상 해킹·모델/structured 실패는 store 호출 전에 차단한다. repo-approved source는 기본 비활성 scheduler가 durable lease/retry job으로 관리한다. 실제 claude_cli/claude-opus-4-8 7 calls와 dev PostgreSQL reviewer 2·variant 3·pass rate 1.0을 거쳐 synthetic payload를 pending_human_approval로 저장했다. G6 canonical drift trigger는 metadata-only incident→DAG→job을 원자 enqueue하고, 실제 pending 후보의 관리자 검수는 catalog 0→1·approval 1과 역할 403·멱등/409·민감정보 비노출을 통과했다. 별도 누수 후보는 422·저장 0이다. 상세: ops/outcome-os-g8-agentic-worker-evidence-2026-08-07.md.
NAS-origin runtime 재검증
SHA 6030a677…c611의 API/Web 이미지는 health 3/3·db/engine true, OpenAPI 126·auth 401·실제 SSE→DB-backed review를 통과했다. 그러나 108/108은 localhost candidate 결과였고 같은 NAS 평문 origin에서는 crypto.randomUUID 예외로 24건이 실패했다. 소스의 randomUuid() 수정은 source-only Vite 6/6·typecheck·build를 통과했다. clean-head controller도 HEAD/tree/archive 2회 결정성·archive-only candidate·patch 무회귀와 commit 전 actual-origin 11-spec rollback gate를 unit 30/30로 고정했고 새 predeploy dump 72698232…5d41c를 보존했다. 두 execute는 모두 승격 전 fail-closed되어 mutation 0이다. 후속으로 UTC timestamp·KST 날짜 계약을 구현해 API 전체 921 passed와 KST 실DB browser 코칭 이력 1/1을 통과했다. 사용자 승인 아래 새 clean commit을 결속하고 동일 NAS-origin 회기 묶음이 GREEN이 되기 전에는 G8 전체를 DONE으로 올리지 않는다.
실제 rollback receipt DONE
runtime+release_gate 전용 executor는 승인·idempotency·artifact·Compose/env/resolved-policy SHA와 exact API/Web image를 일회성 manifest에 결속하고 HMAC journal·crash recovery·previous-image 복구를 고정한다. 2026-08-07 격리 NAS에서 별도 loopback control-plane API(127.0.0.1:8018)와 별도 executor(127.0.0.1:18149)로 실제 image rollback 2회를 실행했다. rollback-old는 receipt nas-g8-723eeef2…로 previous 이미지 79ec…4450/c530…2f28를, restore-current는 receipt nas-g8-2738846c…로 current 이미지 52e0…8b2d/6fdb…f215를 활성화했다. release gate·approval 각각 2회 멱등, lifecycle rollback/executed 2건, 승인 authorize_rollback 2건, silent auto-promotion 0, HMAC journal 6-record 체인 검증, health 3/3·OpenAPI 126·auth 401·Web 200을 확인했다. helper는 image 상속 compose label을 baseline으로 두고 project/service를 격리 override하는 계약으로 검증했다(launch-nas-preview-g8-helpers.py, 37/37). 상세: 런북 · 기계 판독 증거.
+
agentic worker + 운영 환류 + human gate
승인 source의 실제 hash·PII를 engine 호출 전에 검사하고 structured generator 1개, 결과를 공유하지 않는 red-team 2개, variant generator와 variant별 독립 judge 3개 이상을 실행한다. 정답 누수·PII·unknown source claim·open finding·안전/보상 해킹·모델/structured 실패는 store 호출 전에 차단한다. repo-approved source는 기본 비활성 scheduler가 durable lease/retry job으로 관리한다. 실제 claude_cli/claude-opus-4-8 7 calls와 dev PostgreSQL reviewer 2·variant 3·pass rate 1.0을 거쳐 synthetic payload를 pending_human_approval로 저장했다. G6 canonical drift trigger는 metadata-only incident→DAG→job을 원자 enqueue하고, 실제 pending 후보의 관리자 검수는 catalog 0→1·approval 1과 역할 403·멱등/409·민감정보 비노출을 통과했다. 별도 누수 후보는 422·저장 0이다. 상세: ops/outcome-os-g8-agentic-worker-evidence-2026-08-07.md.
NAS-origin runtime 재검증
SHA 6030a677…c611의 API/Web 이미지는 health 3/3·db/engine true, OpenAPI 126·auth 401·실제 SSE→DB-backed review를 통과했다. 그러나 108/108은 localhost candidate 결과였고 같은 NAS 평문 origin에서는 crypto.randomUUID 예외로 24건이 실패했다. 소스의 randomUuid() 수정은 source-only Vite 6/6·typecheck·build를 통과했다. clean-head controller도 HEAD/tree/archive 2회 결정성·archive-only candidate·patch 무회귀와 commit 전 actual-origin 11-spec rollback gate를 unit 30/30로 고정했다. 첫 current clean commit 5221f79e와 fresh dump 6f4b95a7…b529f를 결속한 execute는 API·types·build·candidate DB 후반까지 통과했지만 production entry 차단 테스트 2건으로 110/112에서 승격 전 중단돼 mutation 0이다. entry 수정은 source/production preview 각 2/2를 통과했다. 새 clean commit을 결속하고 동일 NAS-origin 회기 묶음이 GREEN이 되기 전에는 G8 전체를 DONE으로 올리지 않는다.
실제 rollback receipt DONE
runtime+release_gate 전용 executor는 승인·idempotency·artifact·Compose/env/resolved-policy SHA와 exact API/Web image를 일회성 manifest에 결속하고 HMAC journal·crash recovery·previous-image 복구를 고정한다. 2026-08-07 격리 NAS에서 별도 loopback control-plane API(127.0.0.1:8018)와 별도 executor(127.0.0.1:18149)로 실제 image rollback 2회를 실행했다. rollback-old는 receipt nas-g8-723eeef2…로 previous 이미지 79ec…4450/c530…2f28를, restore-current는 receipt nas-g8-2738846c…로 current 이미지 52e0…8b2d/6fdb…f215를 활성화했다. release gate·approval 각각 2회 멱등, lifecycle rollback/executed 2건, 승인 authorize_rollback 2건, silent auto-promotion 0, HMAC journal 6-record 체인 검증, health 3/3·OpenAPI 126·auth 401·Web 200을 확인했다. helper는 image 상속 compose label을 baseline으로 두고 project/service를 격리 override하는 계약으로 검증했다(launch-nas-preview-g8-helpers.py, 37/37). 상세: 런북 · 기계 판독 증거.
diff --git a/docs/ops/backlog-2026-06-26.md b/docs/ops/backlog-2026-06-26.md
index 42ab602..08e5bad 100644
--- a/docs/ops/backlog-2026-06-26.md
+++ b/docs/ops/backlog-2026-06-26.md
@@ -35,6 +35,10 @@
SHA/size/TOC와 DB identity를 별도 증거로 결속한다. agent 자동 rollback은 image/Compose/active-state 범위뿐이며,
적용 migration/data restore는 별도 owner 승인을 받는다. remote release root/Compose mode `0777` P1도 최소권한
preflight로 닫는다. 이때만 G8 전체 DONE이 가능하다.
+ 첫 current clean commit `5221f79e`는 HEAD/tree/archive와 fresh NAS dump를 결속했지만 candidate E2E
+ 110/112에서 production hashed entry를 차단하지 못한 boot-diagnostic 테스트 desktop/mobile 2건으로 승격 전
+ 중단됐다. NAS mutation 0이다. `/src/main.tsx`와 `/assets/index-*.js`를 함께 차단하는 수정은 Vite source 2/2,
+ production preview 2/2를 통과했으며 새 clean commit으로 한 번만 재실행한다.
> B1 완료 항목(학생 리뷰 1~5 척도 320px 반응형 재배치, 셸 구분선, 세션 종료 UX/다크테마, 아바타 SVG 리그 복귀와 래스터 비활성화, SEO/공유 카드, 레이아웃 정렬, 권한 위임,
> 메일링, 아카이브 API, TTS voice map, 빈상태 레이아웃, 평가 실패 복구 UX, SSE 저장, live-coach 표면화,