416 lines
40 KiB
Markdown
416 lines
40 KiB
Markdown
# 테스트·검증 가이드
|
||
|
||
Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타입체크/빌드, Playwright E2E)을
|
||
"무엇을 어떻게 실행하고, 어떤 의존이 필요한가" 기준으로 정리한다. 이 문서는 실제
|
||
`apps/web/package.json`, `apps/web/playwright.config.ts`, `apps/web/e2e/`,
|
||
`apps/api/app/test_*.py`, `apps/api/engine_gateway/test_*.py`,
|
||
`infra/docker-compose.yml`을 읽고 작성했으며, 명령·경로는 그대로 따라 할 수 있다.
|
||
|
||
주 환경은 Windows 11 + PowerShell이다. 아래 명령은 셸 공통(`python -m ...`, `npm run ...`)
|
||
형태로 적었고, 환경변수 지정은 PowerShell/Bash 양쪽 예시를 병기한다.
|
||
|
||
---
|
||
|
||
## 0. 한눈에 보는 검증 매트릭스
|
||
|
||
| 검증 | 작업 디렉터리 | 명령 | DB | API(8000) | 웹(5173) | 엔진GW(9099) | 브라우저 | 현재 통과 |
|
||
|---|---|---|---|---|---|---|---|---|
|
||
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-07-30 전체 실행 432 passed |
|
||
| 엔진 게이트웨이 테스트 | `apps/api` | `python -m pytest engine_gateway/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-07-30 전체 실행 44 passed |
|
||
| 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` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 215 tests / 20 files |
|
||
|
||
핵심 원칙: **단위 테스트(pytest)와 타입체크/빌드는 외부 서비스 없이 단독 실행된다.**
|
||
**E2E만 풀스택(DB+API+웹+브라우저)을 요구한다.** 아래 각 절에서 근거와 절차를 설명한다.
|
||
|
||
---
|
||
|
||
## 1. 백엔드 단위 테스트 (pytest)
|
||
|
||
### 1.1 대상과 구성
|
||
|
||
- 작업 디렉터리: `apps/api`
|
||
- 테스트는 FastAPI `TestClient`와 인메모리 `store.py` 폴백으로 돌기 때문에 **Postgres/엔진
|
||
게이트웨이가 없어도 통과한다.** 별도 `pytest.ini`/`pyproject.toml` 설정은 없고 기본 수집
|
||
규칙(`test_*.py`)을 그대로 쓴다.
|
||
- `pydantic-settings`가 `apps/api/.env`를 자동 로드한다(있으면). 단위 테스트는 `.env` 없이도
|
||
돈다.
|
||
|
||
### 1.2 실행
|
||
|
||
```sh
|
||
# apps/api
|
||
python -m pytest app/ -q # 앱 단위 테스트: 2026-07-30 전체 실행 432 passed
|
||
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트: 2026-07-30 전체 실행 44 passed
|
||
```
|
||
|
||
수집만 빠르게 확인하려면:
|
||
|
||
```sh
|
||
python -m pytest app/ --collect-only -q # 현재: "432 tests collected" (2026-07-30)
|
||
python -m pytest engine_gateway/ --collect-only -q # 현재: "44 tests collected" (2026-07-30)
|
||
```
|
||
|
||
> 참고: 실행 중 `PendingDeprecationWarning: Please use 'import python_multipart'`
|
||
> 경고가 보일 수 있으나 무해하며 통과 결과에 영향을 주지 않는다.
|
||
|
||
### 1.3 `app/` 테스트 파일 (보안·상태머신·평가 중심)
|
||
|
||
| 파일 | 검증 영역 |
|
||
|---|---|
|
||
| `app/test_runtime_policy.py` | 런타임 정책(공개/dev 모드, dev-login 가드) |
|
||
| `app/test_session_turn_persistence.py` | 세션 턴 영속화(DB/인메모리 양쪽) |
|
||
| `app/test_session_share.py` | 회기 리뷰 공유 URL, sanitized 공개 payload, 토큰 폐기 |
|
||
| `app/test_evaluation_persistence.py` | fast-loop 평가 정규화 적재/복원 매핑 |
|
||
| `app/test_orchestrator_masking.py` | 오케스트레이터 PII 마스킹 게이트 |
|
||
| `app/test_state_machine_resistance.py` | 저항엔진 상태머신(openness 전이) |
|
||
| `app/test_rbac_idor.py` | RBAC / IDOR 권한 경계 |
|
||
| `app/test_auth_providers.py` | 인증 프로바이더(SSO/allowlist) |
|
||
| `app/test_admin_ops.py` | 관리자 콘솔 운영 저장 모델(헬스·티켓) |
|
||
| `app/test_persona_review.py` | 페르소나 리뷰 워크플로 |
|
||
| `app/test_voice_service.py` | 음성 캐스케이드 서비스(STT/TTS) |
|
||
| `app/test_voice_ws.py` | 음성 WebSocket 경계 |
|
||
| `app/test_session_digest_worker.py` | M2 session digest worker 요청 계약, accepted-only 적용, raw text 차단, 재실행 방지 |
|
||
| `scripts/check-dev-dashboard-ssot.py --json` | `docs/dev_dashboard.html` 상태 카운트·M2 검증 수치·DONE/GATE stale 문구 guard |
|
||
|
||
### 1.4 `engine_gateway/` 테스트
|
||
|
||
| 파일 | 검증 영역 |
|
||
|---|---|
|
||
| `engine_gateway/test_gateway_model.py` | 게이트웨이 모델 선택·공유 engine contract·SSE 요청/응답 계약(ENGINE_MODE별) |
|
||
| `engine_gateway/test_provider_registry.py` | Claude/Anthropic/Codex/Agy capability 정규화, 기본값, CLI 인자, 잘못된 모델·추론 강도 차단 |
|
||
|
||
---
|
||
|
||
### 1.5 로컬 DB smoke — 저항엔진 openness 곡선
|
||
|
||
저항엔진은 단위 테스트와 별도로 실제 API 경로와 Postgres 저장 상태를 함께 확인할 수 있다.
|
||
이 smoke는 dev-login으로 학습자 세션을 만들고, P1 상담 2개에 공감 발화/조언점프 발화를 각각
|
||
5턴씩 넣은 뒤 `app.session_state`, `app.turns`, `app.turn_client_state`를 직접 조회한다.
|
||
|
||
사전 조건:
|
||
|
||
- API가 DB 연결 상태로 떠 있어야 한다.
|
||
- `apps/api/.env` 또는 환경변수에 `DATABASE_URL`이 있어야 한다.
|
||
- API는 dev-login과 onboarding 저장이 가능한 dev 런타임이어야 한다.
|
||
|
||
```sh
|
||
# repo root
|
||
python scripts/smoke-resistance-openness-db.py \
|
||
--api-base-url http://127.0.0.1:8000 \
|
||
--timeout 240 \
|
||
--out docs/ops/resistance-openness-db-smoke-YYYY-MM-DD.json
|
||
```
|
||
|
||
통과 기준:
|
||
|
||
- 공감 세션은 5턴 안에 `stage=탐색`으로 열리고 `effective_openness`가 0보다 커진다.
|
||
- 조언점프 세션은 `stage=라포`, `effective_openness=0.0`을 유지한다.
|
||
- DB의 `turn_seq`와 `app.turns` 저장 행 수가 API 턴 수와 일치한다.
|
||
|
||
## 2. 웹 타입체크 / 빌드
|
||
|
||
### 2.1 대상과 구성
|
||
|
||
- 작업 디렉터리: `apps/web`
|
||
- `package.json` 스크립트(실측):
|
||
- `generate:api-types` → FastAPI OpenAPI export 후 `src/lib/api.gen.ts` 재생성
|
||
- `check:api-types` → FastAPI OpenAPI export 후 생성 타입 stale 여부 확인
|
||
- `typecheck` → `tsc -b`
|
||
- `lint` → `tsc -b` (현재 lint는 타입체크와 동일)
|
||
- `build` → `tsc -b && vite build`
|
||
- `dev` → `vite`
|
||
- `e2e` → `playwright test`
|
||
- 타입체크/빌드는 **순수 정적 검사**라 DB·API·브라우저가 필요 없다.
|
||
|
||
### 2.2 실행
|
||
|
||
```sh
|
||
# apps/web
|
||
npm install # 최초 1회 (devDependencies: typescript, vite, @playwright/test 등)
|
||
npm run check:api-types # FastAPI OpenAPI ↔ src/lib/api.gen.ts 동기화 확인
|
||
npm run typecheck # tsc -b — 타입 오류 0 확인
|
||
npm run build # cosmetic-filter namespace gate → tsc -b → vite build
|
||
```
|
||
|
||
API DTO를 바꿨다면 먼저 생성 산출물을 갱신한다.
|
||
|
||
```sh
|
||
# apps/web
|
||
npm run generate:api-types
|
||
```
|
||
|
||
### 2.3 CI 계약 게이트
|
||
|
||
GitHub Actions `.github/workflows/api-contract.yml`은 API/Web 계약 관련 파일이 바뀌는
|
||
`pull_request`와 `master` push에서 Python 3.11 API 의존성, Node 22 web 의존성을 설치한 뒤
|
||
`apps/web`의 `npm run check:api-types`를 실행한다. 이 게이트는 FastAPI OpenAPI와
|
||
`src/lib/api.gen.ts`의 drift만 막는 좁은 CI이며, DB·API 서버·브라우저는 필요 없다.
|
||
|
||
---
|
||
|
||
## 3. Playwright E2E (풀스택)
|
||
|
||
### 3.1 의존성 — 왜 풀스택인가
|
||
|
||
Playwright suite에는 **Vite `/api` 프록시를 통해 실제 로컬 FastAPI/DB를 호출하는 full-stack
|
||
E2E**와, 특정 UI/error 상태를 고정하는 **route fixture UI 회귀 테스트**가 함께 있다
|
||
(`apps/web/e2e/README.md`). DB-backed/real-API 증거는 해당 spec이 검증 대상 endpoint를
|
||
`page.route().fulfill()`로 대체하지 않고 실제 API 응답 또는 persisted read-model을 확인한
|
||
경우로 한정한다. 특히 공용 헬퍼 `apps/web/e2e/support.ts`의 `fetchAvailablePersonas()`는
|
||
`source === "database" && !degraded` 페르소나만 사용 가능으로 간주한다.
|
||
|
||
```ts
|
||
// support.ts
|
||
const usable = personas.filter((p) => p.source === "database" && !p.degraded);
|
||
expect(usable.length, ...).toBeGreaterThan(0);
|
||
```
|
||
|
||
즉 **인메모리 degraded 페르소나로는 대부분의 E2E가 통과하지 못한다.** 따라서 E2E는:
|
||
|
||
1. **Postgres(pgvector pg16)** — 실제 DB가 떠 있어야 함
|
||
2. **시드 페르소나** — `AUTO_SEED_PERSONAS=true`(필요 시 `ALLOW_SEED_PERSONA_FALLBACK=true`)
|
||
3. **FastAPI** — `127.0.0.1:8000`에서 수동 기동
|
||
4. **Vite 웹 서버** — Playwright가 자동 기동(아래 3.3)
|
||
5. **Chromium** — `npx playwright install chromium`로 1회 설치
|
||
6. **엔진 게이트웨이(9099)** — **일부 테스트만** 필요(3.5 참고). 레이아웃/시각 게이트는 불필요.
|
||
|
||
### 3.2 사전 준비
|
||
|
||
```sh
|
||
# (1) 브라우저 바이너리 설치 — 최초 1회
|
||
cd apps/web
|
||
npx playwright install chromium
|
||
|
||
# (2) DB 기동 — Docker Desktop 필요. infra/docker-compose.yml의 db 서비스
|
||
# (pgvector/pgvector:pg16). 전체 스택을 띄우려면:
|
||
# docker compose -f infra/docker-compose.yml up -d db
|
||
```
|
||
|
||
### 3.3 API 서버 기동 (시드 포함)
|
||
|
||
PowerShell:
|
||
|
||
```powershell
|
||
# apps/api
|
||
$env:AUTH_DEV_LOGIN_ENABLED = "true" # dev-login 허용 (support.ts가 사용)
|
||
$env:ENVIRONMENT = "dev"
|
||
$env:AUTO_SEED_PERSONAS = "true" # DB에 SEED P1~P3 적재
|
||
$env:ALLOW_SEED_PERSONA_FALLBACK = "true"
|
||
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000
|
||
```
|
||
|
||
Bash:
|
||
|
||
```sh
|
||
cd apps/api
|
||
AUTH_DEV_LOGIN_ENABLED=true ENVIRONMENT=dev AUTO_SEED_PERSONAS=true \
|
||
ALLOW_SEED_PERSONA_FALLBACK=true \
|
||
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000
|
||
```
|
||
|
||
> `.env`에 동일 값을 넣어두면 매번 환경변수를 줄 필요가 없다. DB 연결이 실패하면 main.py
|
||
> lifespan이 인메모리 degraded로 기동되지만(`/health` → `{"status":"degraded","db":false}`),
|
||
> 이 상태에서는 E2E 페르소나 헬퍼가 실패하므로 **E2E용으로는 반드시 DB를 붙여야 한다.**
|
||
|
||
### 3.4 E2E 실행
|
||
|
||
웹 서버는 Playwright `webServer`가 자동으로 띄운다(`PLAYWRIGHT_BASE_URL` 미설정이고
|
||
`PLAYWRIGHT_SKIP_WEB_SERVER`가 없을 때, `npm run dev -- --host localhost --port 5173`).
|
||
|
||
```sh
|
||
# apps/web — API(8000)와 DB가 떠 있는 상태에서
|
||
npm run e2e # 전체: fixture 병렬 단계 → DB/engine 직렬 단계
|
||
npm run e2e:list # 두 단계의 수집 목록·개수만 확인
|
||
npm run e2e:parallel # desktop/mobile route-fixture, workers=4
|
||
npm run e2e:single-run # DB/engine @single-run, workers=1
|
||
npm run e2e:headed # 브라우저 표시
|
||
npm run e2e:ui # Playwright UI 모드
|
||
```
|
||
|
||
특정 스펙/그룹만:
|
||
|
||
```sh
|
||
npx playwright test e2e/session-layout.spec.ts
|
||
npx playwright test --project=chromium-single-run --workers=1 --grep "검색어"
|
||
npx playwright test --grep-invert "@single-run" # 병렬 시나리오만
|
||
npm run e2e:list # 실행하지 않고 단계별 목록·개수만
|
||
```
|
||
|
||
유용한 오버라이드(`playwright.config.ts` 실측):
|
||
|
||
```sh
|
||
PLAYWRIGHT_PORT=5174 npm run e2e # 웹 포트 변경
|
||
PLAYWRIGHT_BASE_URL=http://localhost:5173 npm run e2e # 외부에 이미 뜬 웹 사용(자동기동 끔)
|
||
VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API 직접 지정
|
||
```
|
||
|
||
### 3.5 프로젝트(브라우저 프로파일) 구성
|
||
|
||
`playwright.config.ts`는 `testDir: ./e2e`, `timeout: 30s`, `expect.timeout: 5s`로
|
||
다음 프로젝트를 정의한다. fixture 프로젝트는 `fullyParallel:true`로 실행한다. `npm run e2e`는
|
||
로컬 단일 API/DB/engine 자원을 보호하기 위해 desktop/mobile 단계를 `workers=4`로 먼저 끝내고,
|
||
`chromium-single-run`을 `fullyParallel:false`, `workers=1`로 두 번째 실행한다.
|
||
|
||
| 프로젝트 | 뷰포트/디바이스 | 대상 grep |
|
||
|---|---|---|
|
||
| `chromium-desktop` | 1440×900 | `@single-run`·`@public-auth` 제외 전부 |
|
||
| `chromium-mobile` | Pixel 5 | `@single-run`·`@public-auth` 제외 전부 |
|
||
| `chromium-single-run` | 1280×800 | `@single-run`만(별도 2단계, workers=1 직렬) |
|
||
| `chromium-public-auth` | 1440×900 | `E2E_PUBLIC_AUTH=1`일 때만 활성, 공개 사이트 대상 |
|
||
|
||
- CI(`process.env.CI`)에서는 `retries: 2`, `workers: 1`, `list`+`html` 리포터를 쓴다.
|
||
- 로컬 fixture 단계의 기본 worker는 4다. 전체 러너에서만 `E2E_PARALLEL_WORKERS`로 조정할 수 있고,
|
||
12-worker 전면 병렬은 단일 FastAPI/DB가 포화되어 서로 무관한 요청 timeout을 만들므로 기준선으로 쓰지 않는다.
|
||
- `npx playwright test`를 직접 실행하면 로컬에서 세 프로젝트가 같은 worker pool에 섞일 수 있다.
|
||
전체 게이트는 반드시 `npm run e2e`, focused DB/engine 게이트는 프로젝트와 worker를 명시한
|
||
`npx playwright test --project=chromium-single-run --workers=1 --grep "..."`을 사용한다.
|
||
- 실패 시 trace(첫 재시도)·스크린샷·비디오를 `node_modules/.tmp/`에 남긴다.
|
||
|
||
### 3.6 실측 테스트 개수 (현재)
|
||
|
||
`npx playwright test --list` 기준 **총 215 tests / 20 files**.
|
||
|
||
- **병렬 시나리오**: 166 tests (desktop 83 + mobile 83)
|
||
- **`@single-run` 직렬 시나리오**: 49 tests (DB 영속화·세션 MVP·음성 성공경로·인증 시각 테마·회기말 평가 저장·교수자 명시 재평가 저장·교수자 턴 재평가 저장·교수자 UI 평가 재시도·교수자 사용자별 분석·source pack sync·이론모드 저장·동의 철회 후 voice 차단·브라우저 stream PII 마스킹·음성 transcript 저장 실패 UI 표면화 등)
|
||
- 2026-07-15 전체 검증: `npm run e2e:parallel` **166/166 passed**(2.5분), `npm run e2e:single-run` **49/49 passed**(23.2분). 합계 **215/215 passed**. AI 튜터 DB 영속화는 정상 provider 응답이 2분을 넘을 수 있어 해당 테스트만 같은 실엔진 묶음의 장시간 기준인 240초를 사용한다.
|
||
- `e2e/voice-success.spec.ts`는 직접 `/voice/ws` 캐스케이드, Session 텍스트 턴의
|
||
`POST /voice/speech`, Session 마이크 UI를 함께 검증한다. 브라우저 `<audio>.play()`가 차단된 조건에서도
|
||
Web Audio buffer source 재생이 시작되는지와 Session 마이크 UI가 `audio_end`에 browser voice
|
||
activity/silence 메타를 싣는지 확인한다.
|
||
- 2026-07-30 세션 음성/지연 focused 검증: 실제 로컬 P1 브라우저에서 응답 생성 중 textarea가 enabled이고
|
||
다음 질문 초안이 보존되며, 음성 준비/재생 중에도 보내기가 enabled인 것을 확인했다. 이전 full API 2턴은
|
||
fast-loop evaluator를 직렬로 기다려 완료가 24.17/24.57초였지만, 사후 평가 분리 뒤 실제 UI 응답·전송 해제는
|
||
첫 턴 9.58초, 상주 세션 연속 턴 6.96초였다. `test_session_turn_persistence.py`는 느린 평가 훅이 끝나기 전에
|
||
SSE `done`이 반환되고 이후 normalized 평가가 learner turn에 붙는 계약을 고정한다. Higgs API smoke는
|
||
`higgs-audio-v3-tts-4b`, 24kHz mono WAV 362,924 bytes/7.56초, provider/model 헤더와 synthetic-seed-only
|
||
reference policy를 확인했다. 검증: backend 432 passed, gateway 44 passed, typecheck/API types/build,
|
||
session text stream 1 passed, voice skip/draft 1 passed.
|
||
- 2026-07-13 focused 검증: `PLAYWRIGHT_PORT=5269 npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run --workers=1 --grep "drives one voice turn"` **1 passed**. 같은 로그인에서 텍스트 턴 저장→소유 회기/턴 기반 `/voice/speech`→OpenAI speech 요청→Web Audio buffer 재생 시작을 확인하고, 별도 새 회기에서 마이크 PCM/STT→AI reply→TTS chunk/`tts_end` 경로가 유지되는지 검증한다. 백엔드 voice focused는 **32 passed**이며, 운영 키 직접 smoke는 `gpt-4o-mini-tts`가 98,133-byte MP3(11.68초)를 반환했다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5205 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1` **7 passed**. 실제 브라우저 `openSessionStream()` → `/sessions/{id}/stream` → DB-backed `/review` 축어록 저장 경로, AI 튜터 코칭 이력 저장/재로딩, WebSocket `stt_result` 음성 비언어 메타데이터, Session 마이크 UI가 생성한 voice activity/silence 메타, Phase 3 pre/post 점수의 DB-backed 저장/재조회, 그리고 세션 종료 background deep 평가가 durable DB row로 저장되어 교수자 리뷰가 `평가 완료`로 전환되는지 검증한다. AI 튜터 코칭 이력 검증은 `POST /live-coach` 응답과 DB-backed history payload의 `status=ready`, `latency_ms>0`도 확인해 규칙 기반 `degraded` fallback 200 응답이 정상 AI 코칭으로 통과하지 못하게 한다.
|
||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5238 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "finishes session end evaluation"` **1 passed**. 같은 자동 종료 평가 row가 `/teacher/dashboard`의 `recent_sessions`에서도 `evaluation_status=ready`, `review_ready=true`, `supervisor_state=평가 완료`로 반영되는지 실제 DB/API/엔진으로 검증한다.
|
||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5237 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "explicit teacher session reevaluation"` **1 passed**. 실제 DB/API/엔진에서 학습자 세션과 턴을 만든 뒤 교수자 `POST /eval/sessions/{id}/reevaluate`가 durable `app.session_evaluation` row를 저장하고, `/eval/.../evaluation` 및 `/review`가 `평가 완료`로 반영되는지 검증한다.
|
||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5246 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "teacher turn reevaluation"` **1 passed**. 실제 DB/API/엔진에서 학습자 세션과 턴을 만든 뒤 교수자 `POST /eval/sessions/{id}/turn`이 기존 turn evaluation normalized row를 durable 교체하고, `/review`의 learner turn `techniques`가 재평가 응답과 같은 라벨로 hydrate되는지 검증한다.
|
||
- 2026-07-01 추가 DB-backed UI 검증: `PLAYWRIGHT_PORT=5245 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "manual AI evaluation retry"` **1 passed**. admin engine config를 잠깐 실패 endpoint로 바꿔 실제 `app.session_evaluation` 실패 row를 만든 뒤 원복하고, 교수자 리뷰 UI의 `AI 평가 재시도` 버튼 클릭이 실제 `POST /eval/sessions/{id}/reevaluate` → durable ready row → `/review` `평가 완료` → `/teacher/dashboard` `evaluation_status=ready`까지 이어지는지 검증한다.
|
||
- 2026-07-02 focused 검증: `PLAYWRIGHT_PORT=5255 npx playwright test e2e/kb-source-packs.spec.ts --project=chromium-single-run --workers=1` **1 passed**. 관리자 전용 `POST /kb/live-coach/source-packs/sync`가 인증 없이 401, learner 403, admin 202로 동작하고, 0615 워크북·DSM·공식 상담 지침·자살위험 지침 source pack을 DB-backed evaluator RAG에 sync한 뒤 source-scoped `/kb/eval-grounding`이 503 skip 없이 200을 반환하고 같은 `source_id`만 반환하는지 검증한다.
|
||
- 2026-07-02 focused 검증: `PLAYWRIGHT_PORT=5259 npx playwright test e2e/session-mvp.spec.ts --project=chromium-desktop --workers=1 --grep "AI tutor|stale empty AI tutor quota|quota exhaustion|degraded AI tutor|voice conversation stop"` **7 passed**. route-fixture UI에서 AI 튜터의 stale empty quota가 서버 `GET /live-coach` 재조회 뒤 실제 잔여 기회로 복구되는지, 최신 quota exhaustion 오류가 이전 코칭 카드에 가려지지 않는지, 음성 `reply.conversation_stopped`가 빈 client reply 대신 109 안전 게이트와 socket close를 유지하고 live-coach를 호출하지 않는지 고정한다. 이 fixture들은 UI 회귀 증거이며 DB-backed 엔진 성공 증거가 아니다.
|
||
- 2026-07-03 backend focused 검증: `C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest -p no:cacheprovider apps/api/app/test_session_turn_persistence.py -k "live_coach" -q` **7 passed**. live coach LLM 생성은 성공했지만 `audit.llm_call_log` 저장 확인이 실패한 경우 `status=ready` AI 코칭으로 보이지 않고 규칙 기반 `status=degraded` 코칭으로 내려가는지 검증한다. 기존 DB-backed event 저장 실패 503 회귀와 source metadata/degraded fallback 회귀도 같은 focused 묶음에서 유지한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5251 npx playwright test e2e/session-mvp.spec.ts --project=chromium-desktop --workers=1 --grep "AI tutor|runtime AI tutor"` **4 passed**. mock MVP 코칭 카드에서 `근거 보기` 모달이 source title, `source_pack` kind, `2026-06-15` version, citation을 표시하는지 검증하고, `status=degraded` route fixture는 카드·근거 모달·이력 모달·턴 `C` 마커가 `대체 코칭`/`AI 응답 대체`를 노출하는지 고정한다. 추가 route fixture는 `GET /live-coach` 실패가 빈 이력으로 보이지 않고 alert로 표시되는지, `persistence_source/source=runtime`이 임시 저장 경고로 표시되는지도 고정한다. 이 fixture들은 UI 회귀 증거이며 DB-backed 엔진 성공 증거가 아니다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5252 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1 --grep "pre/post"` **1 passed**. 기존 저장 pre/post 점수를 비운 입력이 `저장된 값 기준`으로 오인되지 않고 invalid 상태, alert, 저장 버튼 disabled로 표면화되는지 검증한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5248 npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1 --grep "pending voice transcript"` **1 passed**. mock WebSocket이 `transcript final` 뒤 `turn_persistence_unavailable` error를 보내면 Session UI가 임시 학습자 발화를 정상 턴으로 확정하지 않고 `저장 실패` 배지와 alert로 표면화하며, 대기 중 내담자 말풍선을 제거하고 텍스트 입력을 복구하는지 검증한다.
|
||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5241 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "AI tutor coaching history"` **1 passed**. 실제 DB/API/엔진 경로에서 AI 튜터 응답이 `status=ready`, `latency_ms>0`로 저장되고, 0615 source pack metadata가 `근거 보기` 모달, DB-backed `/live-coach` 이력, reload 후 `C` 마커 history dialog에 유지되는지 확인한다.
|
||
- 2026-07-01 fixture UI focused 검증: `PLAYWRIGHT_PORT=5229 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1 --grep "retry a failed AI session evaluation|retry fails"` **2 passed**. 교수자 리뷰의 `평가 실패` 상태에서 `AI 평가 재시도` 버튼이 보이고, 재시도 성공 시 완료로 바뀌며 재시도 실패 시 기존 실패 리뷰와 새 실패 사유가 유지되는지 검증한다.
|
||
- 2026-07-01 추가 fixture UI/mixed focused 검증: `PLAYWRIGHT_PORT=5236 npx playwright test e2e/session-review.spec.ts e2e/teacher.spec.ts --project=chromium-desktop --workers=1 --grep "manual AI retry|retry a failed AI session evaluation|retry fails|failed AI session evaluation"` **4 passed**. 평가가 아직 `평가 대기`로 자동 polling 중일 때는 수동 `AI 평가 재시도` 버튼을 숨기고, `평가 실패`일 때만 재시도 버튼을 노출하며, 교수자 pending queue에서도 `하린`/`P6` 종료 회기의 `평가 실패` AI 상태가 수동 `검토 대기` 상태에 묻히지 않는지 검증한다.
|
||
- 2026-07-01 추가 fixture UI focused 검증: `PLAYWRIGHT_PORT=5253 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1 --grep "long-running session evaluation"` **1 passed**. 긴 회기 평가가 기존 짧은 polling window를 넘겨 늦게 ready가 되어도 리뷰 화면이 `평가 대기`에 고착되지 않고 `평가 완료` 리뷰를 반영하는지 검증한다.
|
||
- 2026-07-02 추가 focused 검증: `C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest -p no:cacheprovider apps/api/app/test_teacher_dashboard.py apps/api/app/test_session_turn_persistence.py -k "teacher_cannot_close_review_before_ai_session_evaluation_ready or teacher_can_mark_session_review_closed_with_note or end_session_does_not_reschedule_evaluation_for_already_ended_session or session_evaluation or stale_missing" -q` **8 passed** + `PLAYWRIGHT_PORT=5254 npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1 --grep "manual AI retry|long-running session evaluation|retry a failed AI session evaluation|retry fails"` **4 passed**. 오래된 missing session evaluation이 교수자 대시보드에서도 `평가 실패`로 보이고, 이미 종료된 세션의 중복 `/end`가 평가를 재예약하지 않으며, AI 평가가 ready가 아니면 교수자 리뷰를 `검토 완료`로 닫아 큐에서 숨길 수 없도록 검증한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5229 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "selected CBT theory mode"` **1 passed**. 실제 Session UI에서 `CBT` 이론모드를 선택해 시작한 회기가 `POST /sessions` payload와 DB-backed `GET /sessions/{id}` 상세의 `theory_mode=cbt`로 보존되는지 검증한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5231 npx playwright test e2e/voice.spec.ts --project=chromium-single-run --workers=1 --grep "consent withdrawal"` **1 passed**. 동의 철회 뒤 이미 생성된 `session_id`로 `/api/voice/ws`를 열어도 `consent_required`로 닫히는지 실제 브라우저 WebSocket으로 검증한다.
|
||
- 2026-07-01 focused 검증: `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_dataset_export.py app/test_phase3_artifact_checker.py -q` **16 passed**. X1 exporter가 consented/client-visible `text_masked` 턴만 고르고 raw `sc.text`를 선택하지 않는지, supervisor comment raw text를 JSONL에서 제거하는지, approved/dry-run JSONL shape·row count·privacy·PII gate가 `{}` false-positive를 막는지 검증한다.
|
||
- 2026-07-01 focused 검증: `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_evaluation_persistence.py app/test_session_turn_persistence.py app/test_live_coach_privacy.py -q` **50 passed**. H4 live coach prompt가 recent turns와 fast-loop evaluation dict의 문자열 leaf를 다시 마스킹하고, turn evaluation persistence가 rationale/comment/alternative utterance의 raw 이름·기관·전화번호를 DB insert 전 제거하며, legacy DB row의 빈 `text_masked` fallback과 session-end deep evaluation payload/read-model도 raw PII를 재마스킹하는지 검증한다. 같은 라운드에서 `test_evaluation_persistence.py`를 `IsolatedAsyncioTestCase`로 바꿔 async persistence 테스트가 실제 await되도록 고정했다.
|
||
- 2026-07-01 추가 backend focused 검증: `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_session_turn_persistence.py -k "fast_loop_evaluation" -q` **2 passed** + `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_learner_dashboard.py -k "failed_fast_loop" -q` **1 passed**. fast-loop 평가 훅 예외는 상담 턴 저장을 막지 않되 `evaluation.error`와 리뷰의 `턴 평가 실패` 노트로 표면화하고, evaluator가 반환한 error dict는 성장 점수·최근 피드백에서 제외해 실패가 neutral 0.5로 집계되지 않게 검증한다.
|
||
- 2026-07-01 추가 backend focused 검증: `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_eval_routes.py app/test_evaluation_persistence.py -q` **29 passed**. 단일 턴 `reevaluate_turn`이 결과를 응답 바디에만 두지 않고 `feedback_scores`/`turn_technique`/`turn_client_state`/`supervisor_comment`/`alternative_utterance` row를 교체 저장하며, 저장 실패는 503, 평가 error는 저장 후 502/503으로 표면화하는지 검증한다. normalized evaluation table의 delete RLS policy도 함께 고정한다.
|
||
- 2026-07-01 추가 backend focused 검증: `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_session_turn_persistence.py -k "session_evaluation or stale_missing" -q` **3 passed**. 오래된 종료 회기가 `app.session_evaluation` row 없이 무한 `평가 대기`로 남지 않고, timeout+grace 이후 교수자 read-model에서 `평가 실패`와 `AI 평가 재시도가 필요합니다` 사유로 표면화되는지 검증한다.
|
||
- 2026-07-01 추가 backend focused 검증: `py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_notifications.py app/test_session_turn_persistence.py app/test_evaluation_persistence.py -k "session_evaluation or missing_session_evaluation or scheduled_session_evaluation" -q` **16 passed**. startup recovery가 종료됐지만 evaluation row가 없는 오래된 DB 세션을 evaluator AI context로 찾아 기존 session-end 평가를 재예약하고, 같은 session_id 중복 background 평가를 process-local in-flight set으로 막는지 검증한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5228 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "case worksheet|crisis safety"` **2 passed**. C1 학습자 워크시트 저장→교수자 수정요청 검수와 C2 위기 신호→`app.safety_events`→DB-backed 교수자 안전 알림 큐를 실제 브라우저/API/DB로 검증한다.
|
||
- 2026-07-01 추가 DB-backed 검증: `PLAYWRIGHT_PORT=5244 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "Korean PII|crisis safety event"` **2 passed**. H4는 실제 Session UI stream으로 한국어 이름·기관·전화번호가 포함된 학습자 발화를 보내고, DB-backed `GET /sessions/{id}` 상세와 `/review` 모두 raw 값 없이 `[NAME]`/`[ORG]`/`[PHONE]`으로 마스킹되는지 검증한다. C2는 위기 발화가 교수자 검토용 learner turn으로 저장되지만 client AI 응답 turn은 저장되지 않고, DB-backed 교수자 안전 큐의 109 알림이 유지되는지 확인한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5193 npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1` **2 passed**. MVP 종료/리뷰 흐름과 위기 안전 게이트가 빈 내담자 응답 신호에 덮이지 않는지 검증한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_PORT=5215 npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1` **9 passed**. 교수자 콘솔의 페르소나 검수, 검토 큐, 최근 회기 진입과 별도 `/teach/analysis` 학생 분석 메뉴, 학습자 검색 테이블, 행 펼침, 상세 드릴다운, 전체 회기 탭 전환을 검증한다.
|
||
- 2026-07-01 focused 검증: `PLAYWRIGHT_BASE_URL=http://localhost:5210 npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --grep "shows selected learner analysis"` **1 passed** + `PLAYWRIGHT_BASE_URL=http://localhost:5210 npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --grep "professor (student analysis overview|learner detail analysis)"` **2 passed**. 실제 5210 로컬 스택에서 학생 분석 목록의 사용자 표시명, 상세 기본 페르소나별 회기 탭, 페르소나 행 펼침, 전체 회기 탭 전환, 7개 폭 레이아웃 containment를 검증한다.
|
||
- 2026-07-01 focused 검증: `C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest -p no:cacheprovider app/test_teacher_dashboard.py -q` **10 passed**. 교수자 대시보드 API는 `session_persistence.list_all_sessions()`로 전체 담당 세션을 읽고, 학습자 일반 목록은 `list_recent_sessions()`로 최근 100개 제한을 명시해 한 학생의 최신 회기가 다른 학생 분석 목록을 밀어내지 않는지 검증한다. 또한 ended session summary가 `app.session_evaluation`의 `evaluation_status`, `review_ready`, `supervisor_state`, `evaluation_error`를 별도로 싣고, 평가 실패가 수동 review status와 섞이지 않는지 검증한다.
|
||
- 2026-07-01 focused 검증: `npx playwright test e2e/admin.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 --grep "approved admin without onboarding"` **2 passed**. 승인된 `role=admin` 사용자의 세션에 `admin_access=false`, `onboarding_completed_at=null`이 남아도 `/admin`이 `/onboarding`으로 우회하지 않고 관리 콘솔의 `/admin/*` API를 호출하는지 fixture로 고정한다.
|
||
- 2026-07-15 보태니컬 글래스 UI·SSOT 검증: `npm run check:design-ssot` + `npm run typecheck` + `npm run build` 통과, `npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --reporter=line` 시각 게이트 **15/15**, `npx playwright test e2e/auth-visual.spec.ts --project=chromium-single-run --reporter=line` **1/1**, `npx playwright test e2e/session-layout.spec.ts e2e/learner.spec.ts e2e/session-review.spec.ts --project=chromium-desktop --project=chromium-mobile` **46/46**, 로그인→온보딩 focused desktop/mobile **2/2**. 공통 AppShell GNB, Theme store, Surface variant의 소유권과 전 폭 대시보드/리뷰 탭, 로그인·온보딩 라이트/다크 390/1280px, 고해상도 보태니컬 자산을 함께 고정한다. 라이트 테마도 패널당 복수 굴절 그라데이션, backdrop blur, 헤어라인, 그림자를 computed style로 단언하며 모바일 셸이 보태니컬 배경을 제거하지 않는지 검사한다. 관리자 사용자 표는 semantic table, 정렬 헤더, 1440px 최소 폭과 표 전용 가로 스크롤을 desktop/mobile에서 검증한다.
|
||
|
||
레이아웃·시각 회귀 게이트(핵심 합격선):
|
||
|
||
| 게이트 | 스펙 | 구성 | 개수 |
|
||
|---|---|---|---|
|
||
| 세션 레이아웃 | `e2e/session-layout.spec.ts` | 4 테스트 × (desktop+mobile) | **8 / 8** |
|
||
| 시각 레이아웃 게이트 | `e2e/layout-visual-gate.spec.ts` | `@single-run`, 14개 화면 × 7개 폭 검사 + 다크 테마 assertion + 학습 대시보드 라이트 자산 연결 | **14 / 14** |
|
||
| 인증 테마 게이트 | `e2e/auth-visual.spec.ts` | 로그인·온보딩 × light/dark × 390/1280px, 100vw/100dvh 및 overflow 검사 | **1 / 1** |
|
||
| 레이아웃 포커스(재설계 화면) | `session-layout`·`session-review`·`admin`·`learner`·`settings`·`teacher`, `@single-run` 제외 | desktop+mobile 병렬 | **54** |
|
||
|
||
> `layout-visual-gate`는 7개 폭(390/720/861/900/1024/1280/1440)에서 14개 핵심 화면(페르소나 운영·작성 단계 포함)의 가로
|
||
> 오버플로·잘린 컨트롤·다크 테마 적용을 검사하고 전체 페이지 스크린샷을
|
||
> `node_modules/.tmp/layout-gate/`에 남긴다. 학습 대시보드는 추가로 라이트 테마의 카드·사이드바·
|
||
> 우상단 배경 자산이 실제 computed style에 연결됐는지 확인하고 1200px·390px 라이트 캡처를 남긴 뒤 다크로 복귀한다.
|
||
> `session-layout`은 회기 전/활성 화면이 뷰포트를 벗어나지 않는지, 우측 패널이 코어 영역을
|
||
> 침범하지 않는지, 시작 후 실제 `session_id` URL에서 새로고침해도 활성 회기 상세가 유지되는지,
|
||
> 스트림 실패 시 미저장 전사가 남지 않는지를 검증한다.
|
||
|
||
### 3.7 공개 인증 스모크(선택)
|
||
|
||
`e2e/public-auth-turn.spec.ts`와 `e2e/public-admin-visual.spec.ts`는 공개 사이트
|
||
(`https://vignette.chanpaca.net`)를 직접 타격하는 옵트인 스모크로, 로컬 웹 서버를 띄우지 않는다.
|
||
관리자 시각 스모크는 운영 홈·AI 운영·사용자·권한·티켓의 본문 노출, primary learner 화면의
|
||
`운영 콘솔` 복귀, 브라우저 `pageshow` 탭 복원 뒤 실제 내부 `.vg-main.scrollTop=0`과 heading 가시성을
|
||
함께 검증한다. document `window.scrollY`만 검사하면 관리자 셸의 실제 스크롤 잔류를 놓치므로 금지한다.
|
||
로컬 `admin.spec.ts`의 `EasyList cosmetic filters` 회귀는 공식 목록에 있는 옛 `.ad-root`와
|
||
`.ad-section` 숨김 규칙을 그대로 주입한 뒤에도 중립 `[data-vignette-admin-root]`와 운영 홈 heading이
|
||
보이는지 확인한다. API 200이나 DOM 존재만으로 이 검증을 대신하면 광고 차단기 silent blank를 놓친다.
|
||
`npm run check:cosmetic-filter-safety`는 `src/**/*.{ts,tsx,css}`와 `index.html`을 검사해
|
||
`ad-*`, `ads-*`, `advert*`, `sponsor*` class/id namespace가 프로덕션 코드에 다시 들어오면 빌드를
|
||
즉시 실패시킨다. 탐지기 자체의 양성·음성 fixture는 매 실행마다 먼저 검증하며,
|
||
`npm run check:cosmetic-filter-safety:self-test`로 따로 실행할 수도 있다. 이 검사는 `npm run build`와
|
||
`npm run lint` 앞단에 포함되므로 `scripts/start-public-runtime.ps1`의 실제 웹 빌드도 우회하지 못한다.
|
||
공개 `public-admin-visual.spec.ts` 역시 같은 EasyList 규칙을 첫 paint부터 적용한 상태로 관리자 5경로의
|
||
heading/root 실제 rect·computed visibility, legacy class 0, watchdog/console 오류 0을 확인한다.
|
||
절차는 `apps/web/e2e/README.md` 참고
|
||
(`E2E_PUBLIC_AUTH=1`, `npx playwright codegen ... --save-storage`로 인증 상태 캡처 후
|
||
`E2E_PUBLIC_STORAGE_STATE` 재사용). 캡처한 storage state에는 API 세션 쿠키가 들어 있으니
|
||
민감 정보로 취급한다.
|
||
|
||
### 3.8 배포 전환 청크 복구 스모크(선택)
|
||
|
||
`e2e/chunk-recovery-preview.spec.ts`는 프로덕션 빌드의 로그인 lazy 청크 첫 요청을 강제로 실패시켜
|
||
문서가 정확히 한 번만 다시 로드되고 로그인 라우트가 정상 렌더되는지 검증한다. 로컬 `vite preview` 또는
|
||
공개 도메인을 `PLAYWRIGHT_BASE_URL`로 지정하고 아래처럼 실행한다.
|
||
|
||
```powershell
|
||
$env:E2E_PREVIEW_BUILD = "1"
|
||
$env:PLAYWRIGHT_SKIP_WEB_SERVER = "1"
|
||
$env:PLAYWRIGHT_BASE_URL = "http://127.0.0.1:5262" # 또는 https://vignette.chanpaca.net
|
||
npx playwright test e2e/chunk-recovery-preview.spec.ts --project=chromium-single-run --workers=1
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 전체 검증 순서 권장안 (로컬)
|
||
|
||
```sh
|
||
# 1) 외부 의존 없는 빠른 검증부터
|
||
cd apps/api && python -m pytest app/ -q && python -m pytest engine_gateway/ -q
|
||
cd apps/web && npm run typecheck && npm run build
|
||
|
||
# 2) 풀스택 E2E (DB+API 준비 후)
|
||
# 터미널 A: docker compose -f infra/docker-compose.yml up -d db
|
||
# 터미널 B: cd apps/api && (3.3의 env) python -m uvicorn app.main:app --host 127.0.0.1 --port 8000
|
||
# 터미널 C:
|
||
cd apps/web && npm run e2e
|
||
```
|
||
|
||
엔진 턴 생성이나 관리자 모델 저장까지 보려면(음성/세션 MVP 등 일부 `@single-run`) 현재 코드의
|
||
엔진 게이트웨이를 포트 9099에 띄운다. DB의 `admin_engine_config.engine_url`이 오래 떠 있던 구형
|
||
게이트웨이를 가리키면 `/admin/engine-capabilities`가 404/503이므로 프로세스를 새 코드로 재기동해야 한다.
|
||
게이트웨이가 없으면 `/health`의 `engine:false`이고 실제 턴 생성·모델 저장 시나리오는 실패한다.
|
||
레이아웃/시각 게이트는 게이트웨이 없이도 통과한다.
|
||
|
||
공급자 실증은 capability만 확인하지 말고 최소 한 번 실제 고유 응답까지 확인한다. Codex는
|
||
`gpt-5.6-terra` / Medium, Agy는 `gemini-3.6-flash-high` / High가 목록 기본값과 실행 결과 양쪽에서
|
||
일치해야 한다. Anthropic은 실제 키가 없는 환경에서 unavailable·저장 거부가 정상이다.
|
||
|
||
---
|
||
|
||
## 5. 운영 원칙 — "가짜 DONE" 금지
|
||
|
||
검증 결과는 **실제로 실행해 통과한 명령**만 근거로 보고한다.
|
||
|
||
- "통과했다/완료다"라고 말하려면 그 자리에서 **실행 가능한 검증 명령과 관측된 결과(통과 개수)**를
|
||
함께 제시한다. 예: `python -m pytest app/ -q` → `100 passed`.
|
||
- 의존을 갖춘 검증을 우회하지 않는다. E2E를 DB/API 없이 돌려 놓고 "그린"이라고 보고하지
|
||
않는다. 풀스택을 못 띄웠으면 **"E2E 미실행"**이라고 명시한다.
|
||
- 인메모리 degraded 기동(`db:false`)에서 페르소나 헬퍼가 실패하는 것은 환경 문제이지 코드가
|
||
통과한 것이 아니다. 환경을 고친 뒤 재실행한 결과로만 판단한다.
|
||
- 수집(`--collect-only`)·목록(`--list`)은 "있다"는 근거일 뿐 "통과했다"는 근거가 아니다.
|
||
통과 주장은 실제 실행 출력으로 뒷받침한다.
|