유스케이스 TDD 16테마 스펙과 접근성 제품 결함 수정

- 지원 티켓 작성 UI 신설(설정)·관리자 해결 노트 입력 신설(Admin): 서버 계약은 있었으나
  웹 진입점이 없던 2결함
- a11y: outline 채널 포커스 링(ui/shell css), 세션바 44px 터치 타깃, 청록 하드코딩
  그라디언트를 테마 토큰으로 교체, 설정 라벨/헤딩/대비·리뷰 44px·모바일 오버플로 수정
- uc-*.spec.ts 16테마 239 시나리오 신규(수집 1109 tests/61 files), 기존 스펙 5종 계약 드리프트 교정
- breakpoint-sweep: widthsFor 솎아내기가 실기기 대표 폭(360/390/1024)을 탈락시키는 테스트 결함 수정
  — keep 시드를 전체 DEVICE_WIDTHS로, 3연폭 예외는 솎아내기 발생 여부 기준으로
- 검증: tsc PASS, 병렬 게이트 1040 passed(데스크톱 밀도 충돌 1건 해소 후 focused 68/68 GREEN),
  직렬 게이트 52/5/4 삼각화 — breakpoint(테스트 결함)·kb(낡은 dev API 재기동)·voice(일시적) 해소,
  교사 재평가 2건은 엔진 구독 한도(resets 3pm)로 skip 후 재검증 대기
- 문서/SSOT: HANDOFF·TODO·대시보드·testing 가이드 동기화, 증거 usecase-tdd-2026-08-18.json,
  SSOT 체커 PASS(59)
This commit is contained in:
Yun Chan 2026-08-18 11:47:58 +09:00
parent 4771b97c3a
commit cf899b7f15
37 changed files with 11989 additions and 67 deletions

View file

@ -1,6 +1,6 @@
# Vignette G0~G8 완료 추진 핸드오프
> Updated: 2026-08-12 KST (G8 증거 종료 · current NAS engine incident · G7 external proof)
> Updated: 2026-08-18 KST (엔진 readiness 장애 수정·재승격 · 유스케이스 TDD 239 · 공개 Web 배포)
> Workspace: `D:\workspace\vignette`
> 이 문서는 다음 에이전트가 **G0~G8 아홉 목표 중 마지막 G7 외부 게이트를 닫기 위한** 실행 기준이다.
@ -36,6 +36,30 @@ G8 배포 source 기준선은 branch `master`, HEAD `61a41d1f08239b5f9e99cfae42a
`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 리셋 후 재검증이 열려 있다**.
### 이 문서가 인계된 정확한 중단점
- G8 clean-head execute는 격리 NAS preview만 변경했고 공개 API/DB·물리 마이크는 건드리지 않았다. source HEAD/tree와

View file

@ -31,6 +31,11 @@
고정했고, detached-clean `a73bcd24…`·tree `b02b3a1b…`로 API/cloudflared를 fresh 승격했다. receipt
`47388d58…9b08` passed, public health 3/3·db/engine true·OpenAPI 126·local voice exact, watchdog·로그온 boot
명시 실행 result 0을 확인했다.
2026-08-18에는 06:30~10:05 KST 엔진 readiness 장애(고효율 프로브 타임아웃 실패가 1800초 캐시 +
구독 사용량 소진)를 수정했다. 실패 전용 캐시 TTL 30초 분리·프로브 타임아웃 45초로 교정하고
detached-clean `4771b97c…c880`으로 API/게이트웨이/task를 재승격·재등록해 `LastTaskResult=0`,
공개 health ok·OpenAPI 126을 재확인했다. 승격 절차에 이전 루트 `apps/api/.env` 복사 단계가
필요함을 확인했다(1차 승격 fail-closed·약 4분 다운타임).
남은 자동복구 gate는 실제 Windows 재부팅 smoke와 정기 DB 백업 운영화다.
상세: `ops/public-db-recovery-rehearsal-2026-08-07.md`.
- [ ] **자동 DB 백업·정기 복원 리허설**`scripts/backup-vignette-db.ps1`의 custom dump→TOC 검증→

View file

@ -304,13 +304,15 @@
<p class="pulse-state"><b>현재:</b> 공개 앱과 current 주기 실회기는 GREEN, NAS만 health timeout이다. <b>다음:</b> 승인 relay → NAS 재검증 → G7 mic·human proof.</p>
<details class="pulse-details">
<summary>운영 복구·watchdog·배포 상세 보기</summary>
<p class="pulse-state"><b>2026-08-12 현재 인시던트:</b> 공개 API는 health ok·db/engine true·OpenAPI 126·auth 401과 local voice를 유지한다. NAS 프리뷰는 앞선 3/3 <code>degraded·db=true·engine=false</code>에서 15:24 KST local health 3회 타임아웃으로 악화됐고 정적 로그인 화면만 계속 보인다. NAS API의 고정 <code>ENGINE_URL=http://192.168.0.223:9100</code> 대상 listener가 사라졌으며 공개 엔진은 별도 loopback <code>127.0.0.1:9099</code>에서 정상이다. G8 clean-head candidate 112/112·실제 origin 112/112와 rollback receipt는 완료 증거로 보존하지만, 현 NAS runtime은 relay를 source/secret/task에 결속해 복구하고 health 3/3·auth·OpenAPI·assets·실제 회기를 다시 통과할 때까지 DEGRADED다. 자동 재시작·포트 노출·배포는 실행하지 않았다. G7은 별도로 명시 동의 mic·동시 high-water·human evidence가 남는다.</p>
<p class="pulse-state"><b>2026-08-18 엔진 readiness 장애·수정:</b> 06:30~10:05 KST 공개 API가 <code>engine=false</code>로 약 3.5시간 degraded였다. 원인은 (1) <code>reasoning_effort=high</code> 콜드 프로브가 20초 타임아웃을 넘기면 실패가 성공과 같은 TTL(운영 1800초)로 캐시되는 게이트웨이 결함, (2) 공개 엔진 <code>claude -p</code>가 소유자 Claude 구독을 개발 에이전트 작업과 공유해 사용량 한도 소진 시 생성이 실패하는 운영 경계다. 워치독은 기본 <code>/ready</code>의 캐시 200만 보고 엔진을 healthy로 오판해 API만 재시작하는 루프에 빠졌다. 수정: 실패 전용 <code>ENGINE_READY_FAILURE_TTL_SECONDS</code>(기본 30초) 분리 + 프로브 타임아웃 20→45초, 회귀 4건 신설(게이트웨이 62/62·backend 986). detached-clean <code>4771b97c…c880</code>·tree <code>286723de…c1fa</code>로 API/게이트웨이/워치독·부트 task를 재승격·재등록했고 명시 실행 <code>LastTaskResult=0</code>, 공개 health ok·OpenAPI 126·local voice exact를 재확인했다. 1차 승격은 새 worktree에 <code>apps/api/.env</code>(gitignored 비밀값)가 없어 fail-closed로 실패했다 — 승격 절차에 env 이관 단계가 필요함을 기록한다(약 4분 다운타임).</p>
<p class="pulse-state"><b>2026-08-12 인시던트(NAS relay 복구 대기):</b> 공개 API는 health ok·db/engine true·OpenAPI 126·auth 401과 local voice를 유지한다. NAS 프리뷰는 앞선 3/3 <code>degraded·db=true·engine=false</code>에서 15:24 KST local health 3회 타임아웃으로 악화됐고 정적 로그인 화면만 계속 보인다. NAS API의 고정 <code>ENGINE_URL=http://192.168.0.223:9100</code> 대상 listener가 사라졌으며 공개 엔진은 별도 loopback <code>127.0.0.1:9099</code>에서 정상이다. G8 clean-head candidate 112/112·실제 origin 112/112와 rollback receipt는 완료 증거로 보존하지만, 현 NAS runtime은 relay를 source/secret/task에 결속해 복구하고 health 3/3·auth·OpenAPI·assets·실제 회기를 다시 통과할 때까지 DEGRADED다. 자동 재시작·포트 노출·배포는 실행하지 않았다. G7은 별도로 명시 동의 mic·동시 high-water·human evidence가 남는다.</p>
<p class="pulse-state">2026-08-07 공개 DB는 recovered named volume으로 전환했고 원본 container·dump·volume을 보존했다. 2026-08-12에는 휴면 Gradle daemon의 <code>8001</code> 점유·cloudflared/voice sidecar 중단과 PowerShell 5.1 native stderr 조기 종료 결함을 복구했다. DB mutation 전에 fresh custom dump <code>9cbda31e…0ce5</code>(7,532,625 bytes·TOC 1,492)를 고정했다. 공개 API·cloudflared는 detached-clean <code>a73bcd24…</code>·tree <code>b02b3a1b…</code>로 fresh 교체했고 public health 3/3·db/engine true·OpenAPI 126·local voice exact를 통과했다. 로그온·5분 watchdog 두 task도 같은 root/commit/tree/script SHA에 pin해 명시 실행 결과 0을 확인했다. 공개 runtime receipt는 <code>47388d58…9b08</code>, 무마이크 rehearsal evidence는 voice <code>61af2e98…</code>·runtime <code>c6e8670e…</code>·topology <code>89f1cb86…</code>이며 UUID/email literal 0이다. G7은 배포가 아니라 실제 mic·동시 high-water·human evidence만 남은 external GATE다.</p>
</details>
<div class="phase-rail" id="phase-rail" aria-label="Phase 진행 추정"></div>
<div class="mstrip" aria-label="검증 지표">
<span class="mchip good">G8 candidate/NAS-origin E2E <b>112/112 · 112/112</b></span>
<span class="mchip good">현재 전체 회귀 · gateway <b>58</b> · backend <b>921</b></span>
<span class="mchip good">현재 전체 회귀 · gateway <b>62</b> · backend <b>986</b></span>
<span class="mchip good">유스케이스 E2E 16테마 <b>239 시나리오</b></span>
<span class="mchip good">Outcome clean E2E <b>126</b> · visual <b>15/15</b></span>
<span class="mchip good">Alliance G1 baseline 1.2 <b>9/9</b> · 내부 DONE</span>
<span class="mchip">Tailnet <b>이전 확인 OK</b></span>
@ -1095,7 +1097,7 @@
<tbody>
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr>
<tr><td>Design SSOT / auth visual</td><td><code>npm run check:design-ssot</code> / <code>npx playwright test e2e/auth-visual.spec.ts --project=chromium-single-run --reporter=line</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --reporter=line</code></td><td>SSOT checker passed; login/onboarding light-dark desktop-mobile 1 passed; 14 core screens × 7 widths visual gate 14 passed.</td></tr>
<tr><td>Full Playwright E2E baseline</td><td><code>npm run e2e:parallel</code> / <code>npm run e2e:single-run</code> / <code>npm run e2e:list</code></td><td>2026-08-12 현재 수집은 <b>629 tests / 45 files</b>다. 이 숫자는 수집량이며 현 작업트리 전체 GREEN과 동일하지 않다. G8 clean-head release gate는 candidate 112/112와 실제 NAS-origin 112/112를 통과했다. 이전 단일 120/120과 2026-07-15의 fixture desktop/mobile 166/166 + DB/engine/provider 직렬 49/49 = 215/215는 범위가 다른 역사 기준선으로 보존한다.</td></tr>
<tr><td>Full Playwright E2E baseline</td><td><code>npm run e2e:parallel</code> / <code>npm run e2e:single-run</code> / <code>npm run e2e:list</code></td><td>2026-08-18 현재 수집은 <b>1109 tests / 61 files</b>(유스케이스 16테마 <code>uc-*.spec.ts</code> 239 시나리오 추가). 이 숫자는 수집량이며 현 작업트리 전체 GREEN과 동일하지 않다. G8 clean-head release gate는 candidate 112/112와 실제 NAS-origin 112/112를 통과했다. 이전 단일 120/120과 2026-07-15의 fixture desktop/mobile 166/166 + DB/engine/provider 직렬 49/49 = 215/215는 범위가 다른 역사 기준선으로 보존한다.</td></tr>
<tr><td>Refactor governance P1~P8</td><td><code>ruff check app</code> / <code>pytest -q app</code> / <code>pytest -q engine_gateway</code> / <code>npm run typecheck</code> / <code>npm run check:api-types</code> / <code>npm run check:design-ssot</code> / <code>npm run check:dead-code</code> / <code>npm run check:duplication</code> / <code>npm run build</code> / <code>npm audit --audit-level=high</code> / full Playwright</td><td>Backend 400 passed, gateway 29 passed, web gates/build/audit passed, vulnerabilities 0, production duplication 1 clone/15 lines/0.03%, Playwright 215/215 passed. 상세 근거는 <code>ops/refactor-governance-2026-07-15.md</code>.</td></tr>
<tr><td>API typegen SSOT</td><td><code>npm run check:api-types</code></td><td>Passed; FastAPI OpenAPI → <code>src/lib/api.gen.ts</code> stale check</td></tr>
<tr><td>Outcome &amp; Alliance OS G0</td><td><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider apps/api/app/test_measurement_contract.py apps/api/app/test_runtime_schema_ssot.py -q</code> / <code>scripts/check-measurement-ledger.sql</code> / measurement·API contract checks / web typecheck / DB-backed <code>session-persistence</code> focused E2E 3종</td><td>G0 contract/schema 11 passed, 기존 backend 100 passed, auth 39 passed. Python→JSON Schema→TypeScript→PostgreSQL enum·필수필드 계약이 일치하고 8개 deterministic benchmark가 검증됐다. Live PostgreSQL에서 learner/client/evaluator 가시 행 1/1/2, 교차 누수 0, append-only guard 2를 확인했다. 학습자 턴→교수자 대시보드, 워크시트 검수, 종료 deep 평가→durable 리뷰 E2E는 각각 1 passed. G0/AOS-001~004 완료.</td></tr>

View file

@ -20,7 +20,7 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
| API 타입 생성 체크 | `apps/web` | `npm run check:api-types` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
| 웹 타입체크 | `apps/web` | `npm run typecheck` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
| 웹 빌드 | `apps/web` | `npm run build` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 현재 수집 629 tests / 45 files · 현 작업트리 전체 GREEN 미검증 |
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 현재 수집 1109 tests / 61 files · 현 작업트리 전체 GREEN 미검증 |
핵심 원칙: **단위 테스트(pytest)와 타입체크/빌드는 외부 서비스 없이 단독 실행된다.**
**E2E만 풀스택(DB+API+웹+브라우저)을 요구한다.** 아래 각 절에서 근거와 절차를 설명한다.
@ -271,8 +271,9 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
### 3.6 실측 테스트 개수 (현재)
2026-08-12 `npx playwright test --list` 기준 **현재 수집 629 tests / 45 files**다.
이 숫자는 수집량이지 통과량이 아니다. 현 작업트리 전체 629개 완주는 아직 증거가 없으며,
2026-08-18 `npx playwright test --list` 기준 **현재 수집 1109 tests / 61 files**다
(유스케이스 16테마 `uc-*.spec.ts` 239 시나리오 포함).
이 숫자는 수집량이지 통과량이 아니다. 현 작업트리 전체 1109개 완주는 아직 증거가 없으며,
과거 전체 GREEN 기록과 이번 focused/release gate 결과를 구분해 적는다.
NAS-origin 112건은 전부 실 API/DB 회기가 아니다. release receipt는 `session-layout`·`session-persistence`

View file

@ -0,0 +1,51 @@
{
"schema": "vignette.usecase-tdd-campaign.v1",
"captured_at": "2026-08-18T11:05:00+09:00",
"goal": "사용자 유스케이스 200개 이상을 RED 우선 TDD로 작성하고 실패를 제품 수정 또는 근거 있는 테스트 교정으로 닫는다",
"method": "route-fixture 전용(모든 /api/** catch-all 404 선등록, mocked auth, 실제 AI 엔진 턴 생성 0). 테마별 병렬 작성 후 chromium-desktop(1440x900)/chromium-mobile(Pixel 5) 동시 수집.",
"themes": [
"uc-auth-onboarding(15)", "uc-learner-home-dashboard(16)", "uc-learner-practice-history(14)",
"uc-session-prestart(14)", "uc-session-conversation(14)", "uc-session-timer(14)",
"uc-session-coach-voice(14)", "uc-session-review-core(15)", "uc-review-alliance-pulse(14)",
"uc-review-rupture-repair(15)", "uc-practice-g4-g5(14)", "uc-settings-consent(15)",
"uc-teacher-console(14)", "uc-admin-console(14)", "uc-error-resilience-shell(15)",
"uc-a11y-mobile(22)"
],
"scenario_total": 239,
"collected_inventory": "1109 tests / 61 files (npx playwright test --list, 2026-08-18)",
"red_findings_fixed_in_product": [
{
"area": "settings/support-ticket",
"finding": "서버 계약 POST /users/support-tickets가 있는데 웹 어디에도 티켓 작성 진입점이 없었다",
"fix": "설정 지원 요청 섹션에 새 요청 작성 폼(제목/내용/전송)과 접수 확인 상태 추가 (Settings.tsx, api.ts, settings.css)"
},
{
"area": "admin/ticket-resolution-note",
"finding": "AdminTicketPatch.resolution_note 계약과 학습자 화면의 해결 메모 표시가 있는데 관리자가 노트를 남길 입력 수단이 없었다",
"fix": "관리자 티켓 액션에 해결 노트 textarea를 추가하고 해결 시 trim 후 전송 (Admin.tsx, admin-console.css)"
},
{
"area": "a11y/focus-visibility",
"finding": "글래스 인셋 box-shadow가 전역 :focus-visible 링을 덮어 로그인·교수자 홈에서 키보드 포커스가 보이지 않았다",
"fix": "outline 채널로 3px focus ring 추가 (ui.css, shell.css — WCAG 2.4.7)"
},
{
"area": "a11y/labels-headings-contrast",
"finding": "설정 계정 입력 라벨 미연결, 설정/리뷰 h1 헤딩 구조 결함, 설정 저장 CTA 대비 미달, 리뷰 44px 미달·설정 모바일 가로 오버플로",
"fix": "라벨 연결·헤딩 레벨 정정·대비 토큰 보정·터치 타깃/오버플로 CSS 수정 (Settings.tsx, SessionReview.tsx, settings.css, session-review.css, session.css)"
}
],
"test_side_corrections": "기존 스펙 5파일(full-sweep-admin-ai, full-sweep-auth, full-sweep-session-review, full-sweep-shell, readiness, teacher)의 실패는 제품 회귀가 아니라 계약 확장(/admin/usage 필드 추가 등)·StrictMode 이중 mount·hit-test 계약을 반영하지 못한 테스트 드리프트로 판별해 교정했다. 어설션 약화 없음.",
"verification": {
"web_typecheck": "tsc -b PASS",
"backend_pytest": "986 passed (engine_gateway 62 포함)",
"a11y_theme_rerun": "uc-a11y-mobile 44/44 (desktop 22 + mobile 22)",
"full_parallel_gate": "docs/HANDOFF.md 2026-08-18 절이 최종 수치를 소유한다",
"serial_single_run_gate_followup": {
"initial_run": "2026-08-18 11:10-11:22 KST npm run e2e:single-run: 52 passed / 5 failed / 4 skipped (12.1m)",
"triage_and_rerun": "① breakpoint-sweep '실기기 대표 폭 누락'은 widthsFor 솎아내기가 DEVICE_WIDTHS 를 boundary 중복이라고 제외 후 상한 솎기로 탈락시키는 테스트 결함(제품 아님) — keep 시드를 전체 DEVICE_WIDTHS 로 교정하고 3연폭 예외 조건을 솎아내기 발생 여부 기준으로 고침 → focused 1 passed. ② kb-source-packs 500은 게이트웨이 수정 커밋(4771b97c) 이전에 기동한 낡은 dev API(:8000) 프로세스가 원인 — 재기동 후 focused 1 passed. ③ voice-success 2 tests 재실행 2 passed(일시적 환경). ④ session-persistence 교사 재평가 2건은 엔진 실생 턴 필요 — Claude 구독 세션 한도('resets 3pm Asia/Seoul')로 엔진 가용 게이트가 skip 처리.",
"pending": "session-persistence 교사 재평가 2건(@single-run) — 엔진 구독 한도 리셋(2026-08-18 15:00 KST) 후 e2e:single-run 재검증으로 닫는다. 초기 실패 당시 엔진 502 본문: You've hit your session limit · resets 3pm (Asia/Seoul)."
}
},
"boundaries": "route fixture 검증이므로 실제 로그인/API/DB 증거가 아니다. 실 DB-backed 증거는 기존 @single-run·closed-loop 스펙이 소유한다."
}