diff --git a/apps/web/e2e/self-directed-learning-loop.spec.ts b/apps/web/e2e/self-directed-learning-loop.spec.ts
index 56b34df..cc41396 100644
--- a/apps/web/e2e/self-directed-learning-loop.spec.ts
+++ b/apps/web/e2e/self-directed-learning-loop.spec.ts
@@ -1324,7 +1324,7 @@ test.describe("학습자 자기주도 전체 루프 — 실제 src UI / route fi
await page
.getByRole("button", { name: "이번 회기 연습 근거 확인" })
.click();
- const observation = page.locator(".dp-runtime-observation");
+ let observation = page.locator(".dp-runtime-observation");
await expect(observation).toBeVisible();
await expect(
observation.getByRole("heading", {
@@ -1374,6 +1374,16 @@ test.describe("학습자 자기주도 전체 루프 — 실제 src UI / route fi
await observation.scrollIntoViewIfNeeded();
await capture(page, testInfo.project.name, "repractice-observation");
+ await page.reload();
+ observation = page.locator(".dp-runtime-observation");
+ await expect(observation).toBeVisible();
+ await expect(observation).toContainText("원장 반영됨");
+ await expect(observation).toContainText("현재 원장 상태");
+ await expect(observation).toContainText(
+ "이미 기록된 이 회기 근거를 원장에서 다시 불러왔습니다.",
+ );
+ await expect(observation).not.toContainText("학습자만 실행");
+
await observation
.getByRole("button", { name: "반영 상태 다시 확인" })
.click();
diff --git a/apps/web/src/pages/session-review/DeliberatePracticeCard.tsx b/apps/web/src/pages/session-review/DeliberatePracticeCard.tsx
index 26195ee..d871dc7 100644
--- a/apps/web/src/pages/session-review/DeliberatePracticeCard.tsx
+++ b/apps/web/src/pages/session-review/DeliberatePracticeCard.tsx
@@ -216,18 +216,24 @@ function latestEpisodeFor(
prescription: PracticePrescriptionItem,
episodes: PracticeEpisodeItem[],
): PracticeEpisodeItem | null {
- const matching = episodes.filter((episode) => {
- const payload = episode.assessment_payload as Record
{result?.idempotent_replay ? "같은 회기 근거를 중복 없이 확인했습니다." + : hydratedFromLedger + ? "이미 기록된 이 회기 근거를 원장에서 다시 불러왔습니다." : "새 관찰 근거를 원장에 반영하고 최신 진행 상태를 다시 불러왔습니다."}
diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 0821f43..1b9ebca 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -685,7 +685,10 @@ route fixture는 90건이므로 전체 숫자를 실제 회기 폐루프로 부 전용 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했고 cleanup은 매번 GREEN이었다. 현재 소스는 durable turn 2개 poll만 완료 신호로 쓰며 첫 GREEN receipt는 아직 없다. +fail-closed했다. 4차 clean run은 6.3분에 동일 학습자 SSE·review ready·G4 0→1까지 통과했지만 full reload 뒤 persisted +episode를 카드가 hydrate하지 않아 이미 기록된 원장을 `학습자만 실행`으로 표시하는 제품 결함을 검출했다. 네 run 모두 +public/NAS/active DB 접촉 0, cleanup GREEN이다. UI는 현재 session+prescription episode를 `현재 원장 상태`로 hydrate하도록 +수정했고 route fixture desktop/mobile 6/6·typecheck를 통과했다. full runner는 재시도하지 않았으므로 첫 GREEN receipt는 아직 없다. ## 9. 핵심 변경 파일 diff --git a/docs/TODO.md b/docs/TODO.md index 6d049fe..f1def2a 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -142,8 +142,11 @@ > 매시간 `vignette-e2e` heartbeat 자동화는 ACTIVE다. 공개/NAS 화면과 health는 읽기 전용으로 보여주고, > 소스 material milestone에서만 전용 disposable 회기 E2E를 실행한다. runner의 clean HEAD/tree·로컬 npipe Docker· > loopback 동적 포트·sentinel·exact cleanup 계약과 unit 11/11은 완료됐다. 최초 세 full run은 각각 SSE body 재읽기, -> durable turn 전 종료, 비종료 SSE의 `response.finished()` 대기라는 harness 결함을 fail-closed로 잡았고 매번 자원 잔여 0이었다. -> 현재 소스는 durable turn 2개 poll만 완료 신호로 사용하며, 다음 clean single run의 첫 GREEN receipt가 남았다. +> durable turn 전 종료, 비종료 SSE의 `response.finished()` 대기라는 harness 결함을 fail-closed로 잡았다. 4차 clean run은 +> 6.3분 안에 동일 학습자 SSE·review ready·G4 원장 0→1까지 통과한 뒤, full reload가 persisted episode를 화면에 hydrate하지 +> 못해 이미 기록된 상태를 `학습자만 실행`으로 되돌리는 제품 결함을 잡았다. 네 번 모두 public/NAS/active DB 접촉 0·자원 잔여 0이다. +> 현재 UI는 현재 session+prescription의 persisted episode를 `현재 원장 상태`로 복원하며 route fixture desktop/mobile 6/6을 통과했다. +> full runner는 재시도하지 않았으므로 다음 clean single run의 첫 GREEN receipt가 남았다. > 증거: [배포 증거](./ops/nas-preview-deployment-evidence-2026-08-07.md), [기계 판독 증거](./ops/evidence/nas-preview-current-deploy-2026-08-07.json), [브라우저 증거](./ops/evidence/nas-preview-live-turn-2026-08-07.png). 이는 G7 물리 마이크·독립 사람 평가 증거를 대체하지 않는다. - [ ] **claude_cli ↔ Anthropic API live 동일성** — provider 라우팅·모델 탐색·설정 저장 경로는 구현 완료. 남은 게이트는 연구팀/기관 `ANTHROPIC_API_KEY`를 게이트웨이 호스트에 주입한 뒤 같은 프롬프트의 live 응답·계량·오류 표면화를 비교하는 것이다. diff --git a/docs/dev_dashboard.html b/docs/dev_dashboard.html index 5baacf5..3f95076 100644 --- a/docs/dev_dashboard.html +++ b/docs/dev_dashboard.html @@ -667,6 +667,7 @@192차 적용(2026-08-09): G8 clean-head 최종 승격을 완료했다. source HEAD 61a41d1f…6af·tree 87dec55d…3b77에서 2회 동일하게 만든 archive 4d15d055d74f…119d4d를 active release로 고정했고 candidate session E2E 112/112와 실제 http://100.116.83.60:8088 평문 NAS-origin browser review 112/112(11 specs, desktop/mobile/single-run)를 모두 통과했다. 실행 이미지는 API d5021950…e4b1·Web 9796c092…4b36이며 health는 status=ok·db=true·engine=true, 비인증 auth 401, OpenAPI 126을 재확인했다. execute 직전 fresh custom dump 36ec8748…24db8(1,097,100 bytes, TOC 1,738 / TABLE DATA 129)를 보존했고 이전 active 6030a677…c611과 이전 이미지는 rollback 기준으로 남겼다. 앞선 fail-closed·verified rollback 이력은 삭제하지 않는다. 이 증거로 G8을 DONE으로 승격하며 남은 Outcome OS gate는 G7 external proof다.
193차 적용(2026-08-09): 학생 화면의 320×568 신뢰성 게이트를 닫았다. 처방 재연습 prestart CTA를 첫 화면에 유지하고, active 회기의 마이크·일시정지·종료를 모든 밀도 구간에서 최소 44px로 고정했으며, 대시보드의 긴 privacy token과 owner grid가 320px 문서를 384px까지 넓히던 overflow를 제거했다. typecheck, session-layout desktop/mobile 8/8, dev-dashboard desktop/mobile 10/10, 자기주도 focused 16/16을 통과했다. 동시에 NAS 112건을 실 API/DB 22와 route fixture 90으로 정직하게 분리하고, clean HEAD/tree·전용 disposable stack·동일 학습자 SSE→review→G4/G5 0→1→1·exact cleanup을 강제하는 주기 runner를 추가했다. unit 11/11과 Compose preflight는 통과했다. G7 human pack은 category·κ≥0.70·preregistration·paired completeness·결측 ITT를 캡처 전에 검증해 부적합 pack이면 마이크를 열지 않는다.
194차 적용(2026-08-10): 주기 runner의 최초 세 full run은 모두 전용 clean stack에서 fail-closed했다. 1차는 성공 SSE의 body를 다시 읽는 CDP 오류, 2차는 UI 2턴만 보고 durable 저장 전에 종료해 리뷰가 기록 없음, 3차는 서버가 의도적으로 닫지 않는 SSE에 response.finished()를 기다려 20분 상한에 도달한 하네스 결함이었다. 세 번 모두 fixture review-ready·stack health는 통과했고 공개 8001·실 DB 55432·engine 9099·NAS 접촉 0, container/volume/network/listener/temp 잔여 0이었다. 현재 소스는 자동 retry 0과 GET /sessions/{id} durable turn 2개 poll만 완료 신호로 사용한다. 같은 단계 반복 원칙에 따라 이번 턴에는 더 실행하지 않으며, 다음 clean single run의 첫 GREEN receipt 전까지 주기 실회기 검증을 완료로 표기하지 않는다.
195차 적용(2026-08-10): clean HEAD 13d1c39a…·tree beb93801…의 4차 수동 full run은 앞선 SSE hang을 제거해 6.3분에 종료했고, 실제 동일 학습자 SSE·review ready·G4 첫 POST와 원장 count 0→1까지 통과했다. 이후 full page reload에서 이미 저장된 현재 회기 episode를 카드가 hydrate하지 않아 원장 반영됨 대신 학습자만 실행으로 되돌아가는 제품 정직성 결함을 fail-closed했다. receipt periodic-learner-e2e-20260810-003150.json은 공개·NAS·active DB 접촉 0과 container/volume/network/listener/temp 잔여 0을 증명한다. UI는 현재 session+prescription의 persisted episode를 read model에서 복원해 현재 원장 상태와 재확인 행동을 표시하도록 수정했고, 실제 reload를 포함한 자기주도 route fixture desktop/mobile 6/6·typecheck를 통과했다. full runner는 재시도하지 않았으므로 첫 GREEN receipt와 자동 주기 승격은 여전히 미완료다.
docker build -f apps/api/Dockerfile . / docker run ... python -c "import app.main" / docker build -f apps/web/Dockerfile apps/webnode_modules; API image excludes local .env files.python scripts\check-deploy-preflight.py --skip-db --env-file infra\.env.example --allow-placeholder-secrets / DB mode with local DATABASE_URLdata/kb source pack, env template keys, and DB readiness (current_user=vignette). DB mode can additionally check app-role DSN with --require-app-role and now verifies session read-model columns including app.turns voice metadata columns(audio_ref/silence_ms/speech_rate/barge_in/provider_events) plus worksheet review columns.docker compose -p vignette-packaging-smoke -f infra/docker-compose.yml up -d --build + proxy /api/healthhttp://localhost:18080/api/health 200 with db:true, engine:true, engine_mode:"claude_cli". Smoke volumes/network removed after run.http://100.116.83.60:8088 / vignette-e2e 매시간 heartbeat / 배포 증거 / 기계 판독 증거61a41d1f…6af·tree 87dec55d…3b77의 archive 4d15d055…119d4d를 전용 Compose 프로젝트·포트·네트워크·named volume에 승격했다. candidate 112/112와 실제 NAS-origin 112/112는 통과했지만 영수증은 실 API/DB 22건과 route fixture 90건을 분리한다. 자동화는 공개/NAS 화면과 health를 읽기 전용으로 매시간 보여준다. material milestone의 동일 학습자 SSE→review→G4/G5 actual 폐루프는 clean HEAD/tree·전용 engine/DB/API/Web·보호 포트 거부·sentinel·exact cleanup runner가 소유하며 unit 11/11은 통과했다. 세 full run은 하네스 결함을 fail-closed로 찾고 cleanup 3/3 GREEN이었으나 첫 실제 폐루프 GREEN은 아직 없다. current 소스는 durable turn 2개 poll만 완료 신호로 사용한다. release gate·NAS preflight·배포 SHA 변경 때만 프리뷰를 갱신한다. G7 외부 mic/provider/human 증거는 별도다.http://100.116.83.60:8088 / vignette-e2e 매시간 heartbeat / 배포 증거 / 기계 판독 증거61a41d1f…6af·tree 87dec55d…3b77의 archive 4d15d055…119d4d를 전용 Compose 프로젝트·포트·네트워크·named volume에 승격했다. candidate 112/112와 실제 NAS-origin 112/112는 통과했지만 영수증은 실 API/DB 22건과 route fixture 90건을 분리한다. 자동화는 공개/NAS 화면과 health를 읽기 전용으로 매시간 보여준다. material milestone의 동일 학습자 SSE→review→G4/G5 actual 폐루프는 clean HEAD/tree·전용 engine/DB/API/Web·보호 포트 거부·sentinel·exact cleanup runner가 소유하며 unit 11/11은 통과했다. 1~3차 full run은 하네스 결함, 4차는 G4 첫 0→1 뒤 reload에서 persisted ledger UI가 초기화되는 제품 결함을 fail-closed했고 cleanup 4/4 GREEN이었다. read model hydrate 수정은 route fixture desktop/mobile 6/6을 통과했지만 첫 실제 폐루프 GREEN은 아직 없다. release gate·NAS preflight·배포 SHA 변경 때만 프리뷰를 갱신한다. G7 외부 mic/provider/human 증거는 별도다.scripts/run-g7-external-proof-window.py · 준비 문서b34623f3…와 fresh receipt 9f8d1941…로 완료했다. Cloudflare가 Python 기본 User-Agent를 403으로 거부하는 경계도 브라우저 호환 header와 회귀 4/4로 고정했다. 30초 rehearsal은 세 leg 7 samples를 모두 통과했으며 마이크를 열지 않았다. 남은 선행 조건: 사용자의 물리 장치 선택과 명시 동의, 독립 human voice-gain pack이다.run-g7-external-proof-window.py → check-g7-external-proof.pyexit 0·gate_closed=true에 결속되고, browser Origin과 API transport host는 별도 allowlist로 검증된다. 세 artifact 교집합은 ≥3000s + 120초 margin이고 Windows proof는 detached-clean HEAD/tree·tool SHA·psutil==6.1.1을 pin한다. clean-source 배포와 무마이크 rehearsal은 완료했다. 남은 것은 물리 마이크 3,120초, 동시 runtime/topology, 독립 human pack을 합친 4-artifact 실증뿐이다.vignette-preview-20260807 · run-outcome-os-release-agent.py4d15d055…119d4d, source HEAD/tree 61a41d1f…6af/87dec55d…3b77, API/Web d5021950…e4b1/9796c092…4b36 exact running. candidate 112/112와 actual NAS-origin 112/112, health ok·db/engine true·auth 401·OpenAPI 126을 통과했다. fresh dump 36ec8748…24db8 1,097,100 bytes, TOC 1,738 / TABLE DATA 129와 previous active 6030a677…c611을 보존했다. release agent의 DB restore 비소유·image/Compose/active-state rollback 경계는 유지한다.