Skip to content

feat: 실제 로컬 Server·Brain 연결 텍스트 대화 인수 검증 — local stack 스크립트·실행 문서 (1.96) - #316

Merged
chowon442 merged 2 commits into
devfrom
worktree-task-1.96
Oct 20, 2026
Merged

chowon442 merged 2 commits into
devfrom
worktree-task-1.96

Conversation

@chowon442

@chowon442 chowon442 commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

📌 관련 이슈

🏷️ PR 타입

  • ✨ 기능 추가 (Feature) — 로컬 실제 연결 stack
  • 🐛 버그 수정 (Bug Fix)
  • ♻️ 리팩토링 (Refactoring)
  • 🗑️ 코드 제거 (Chore)
  • 📝 문서 수정 (Documentation)
  • 🎨 스타일 변경 (Style)
  • ✅ 테스트 추가 (Test) — 실제 연결 인수 검증(아래)

📝 작업 내용

  • scripts/local-stack/local-stack.sh (up·token·status·down): 격리 Postgres·Redis(proovy-postgres-t196:29632, proovy-redis-t196:29679, tmpfs) → Server(local profile bootJar, :8080, Server 연동 테스트와 같은 DB 형태: proovy_server Flyway + Brain schema + role-boundary.sql) → proovy-brain testkit supervisor(실제 Brain API·graph·store·SSE, 모델은 offline seat) → 검증용 사용자·access token. 운영 API·DB·GCS·OAuth 미사용, token·password는 실행마다 새로 만드는 개발 전용 값.
  • README "로컬 Server·Brain stack": 실행법, localhost로 접속(127.0.0.1이면 POST 403), token 로그인, 요청 한도·scenario 재현, dev proxy 소켓 끊김 한계.
  • vite.config.ts: proxy가 브라우저 Origin을 그대로 넘긴다는 주석 정정(코드 변경 없음).
  • 앱 코드(src/)는 바꾸지 않았다 — 텍스트 경로만으로 완료 검증, source viewer·업로드(S09)·출처 chip(S11) 선행 구현 없음.

🔬 인수 검증 (스펙 08 Verification)

기준: Front f8a6e30(= dev 306c55a + 이 PR), Server origin/dev b576af3(#243 merge commit, git archive → bootJar), Brain proovy-brain dev 1be0945 testkit. Front pin(browser-run-fixtures-v1 @303a9e4) 154개 파일 sha256이 Brain testkit contract_files와 전부 일치(차이 0). 브라우저: Playwright 1.60 headless Chromium, 시나리오마다 새 사용자. Brain scenario·hold는 브라우저 POST /api/conversations의 requestId(= internal request_id)로 하네스가 testkit control route에 등록 — 브라우저는 Server(dev proxy)만 호출.

# Verification 결과 근거
1 첫 텍스트 요청의 원자적 Note 생성·진행→검증 완료→canonical 응답, 직접 차감·v2 미호출 ✅ 실제 연결 A: 홈 첫 질문 → POST /api/conversations(noteId 없음) → /app/chat/{noteId} → Activity(풀이 작성 완료·단계 검증 진행 중) → hold 동안 AI 응답 0개 → release 뒤 검증된 풀이·step 마커·검증 요약. network 6개 시나리오 전체: POST /api/notes·/api/credits/use·v2(/api/conversations/{id} 비-messages)·Brain 포트(2968x)·운영 도메인 0건. 호출된 API는 POST /api/conversations·GET /api/conversations/{id}/messages·GET /api/ai-runs/{runId}/events·POST /api/ai-runs/{runId}/cancel과 notes/users 조회뿐
2 재접속에서 같은 Activity·page/block 순서 복원, 중복 박스·검증 전 본문 없음 ✅ 실제 연결 B: 진행 중 새로고침 → 같은 Activity 2행·순서, AI 응답 0. 브라우저↔dev server SSE 소켓 강제 단절(5초 차단) → "연결이 끊겨 다시 연결하는 중이에요" → backoff 재시도 2회 실패 뒤 GET /events Last-Event-ID: {runId}:4로 재접속 → 완료 뒤 박스 1개·응답 1개·Activity 2행. 완료 뒤 새로고침도 응답 1개
3 취소가 완료 Activity와 draft 유지, 새 질문 자동 전송 없음 ✅ 실제 연결 C: Active Run 동안 draft 입력·Enter → 전송 없음(정지 버튼만). "답변 중단" → +123ms "중단 중"(비활성), 이때 Enter → POST 없음. run.canceled 뒤 풀이 작성 완료 유지·단계 검증 취소됨·"중단됨 · 답변 생성을 중단했습니다.", draft "다음 질문 초안" 유지, 6초 대기 동안 POST 추가 0. (Server→Brain 취소는 실제로 ~0.8초: curl로 run.canceling→run.canceled 확인)
4 접기/펼치기·상태 문구·정답 숨기기·unsupported 안내·direct/supporting ✅ 실제 연결 + 일부 fixture 실제: Activity 박스 접기/펼치기(aria-expanded true→false), 상태 문구(진행 중·완료·중단 중·취소됨·중단됨), unsupported_only 고정 안내, 미검증 공개(inconclusive), 혼합 notice(mixed), 추가 정보 요청(clarification), direct 검증 요약. fixture 근거(offline seat가 만들지 않음): supporting 요약 AssistantResponseView.test.tsx "검증 요약 direct/supporting", thread_like page 접기/펼치기 "fixture 응답 substantive: main은 펼치고 thread_like는…", 정답 숨기기 "정답 숨기기" — Front CI test
5 텍스트 경로만으로 완료 검증, source viewer 선행 구현 없음 ✅ 이 PR은 src/ 무변경. 위 검증은 텍스트 질문만으로 수행
시나리오별 /api 요청 집계 (원자료: network/A~F.tsv)
시나리오 method path (status) × 횟수
A GET /api/ai-runs/{runId}/events (200) ×1
GET /api/conversations/{id}/messages (200) ×1
GET /api/notes (200) ×9
GET /api/notes/tools (200) ×1
GET /api/notes/{id} (200) ×8
GET /api/users/me (200) ×6
POST /api/conversations (200) ×2
B GET /api/ai-runs/{runId}/events (200) ×2
GET /api/conversations/{id}/messages (200) ×3
GET /api/notes (200) ×10
GET /api/notes/tools (200) ×3
GET /api/notes/{id} (200) ×10
GET /api/users/me (200) ×7
POST /api/conversations (200) ×1
C GET /api/ai-runs/{runId}/events (200) ×1
GET /api/conversations/{id}/messages (200) ×1
GET /api/notes (200) ×7
GET /api/notes/tools (200) ×1
GET /api/notes/{id} (200) ×4
GET /api/users/me (200) ×4
POST /api/ai-runs/{runId}/cancel (200) ×1
POST /api/conversations (200) ×1
D GET /api/ai-runs/{runId}/events (200) ×1
GET /api/conversations/{id}/messages (200) ×1
GET /api/notes (200) ×13
GET /api/notes/tools (200) ×1
GET /api/notes/{id} (200) ×16
GET /api/users/me (200) ×10
POST /api/conversations (200) ×4
E GET /api/ai-runs/{runId}/events (200) ×1
GET /api/conversations/{id}/messages (200) ×1
GET /api/notes (200) ×9
GET /api/notes/tools (200) ×1
GET /api/notes/{id} (200) ×8
GET /api/users/me (200) ×6
POST /api/conversations (200) ×2
F GET /api/ai-runs/{runId}/events (200) ×1
GET /api/conversations/{id}/messages (200) ×1
GET /api/notes (200) ×13
GET /api/notes/tools (200) ×1
GET /api/notes/{id} (200) ×16
GET /api/users/me (200) ×10
POST /api/conversations (200) ×4
POST /api/conversations (429) ×1

금지 호출(POST /api/notes·/api/credits/use·v2·Brain 포트·운영 도메인) 0건. B~F의 origin localhost:5197은 SSE 단절용 TCP 중계(뒤는 같은 Vite dev server). Playwright 하네스: harness/

task success-criteria 추가 항목

  • 같은 Note 두 번째 질문 (A-4) — 같은 /app/chat/1에 응답 2개
  • 409 activeRun 복원 (E) — 탭 2가 run 없을 때 연 같은 Note에서 전송 → 409 → "이 노트에서 진행 중인 답변이 있어요 / 진행 중인 답변을 이어서 보여 드려요. 보낸 질문은 입력창에 남겨 두었어요." + 탭 1 run의 Activity 진행 표시·정지 버튼, 완료 뒤 두 탭 모두 응답 2개
  • 요청 한도 안내 (F) — RATE_LIMIT_MAX_REQUESTS=4(사용자당 1분)로 띄워 POST 200×4 → 429 → "요청이 많아 잠시 기다려야 해요 / 44초 뒤에 다시 보낼 수 있어요." draft 유지, 전송 버튼 aria-disabled, 대기 중 클릭해도 POST 없음
  • Vite proxy SSE buffering 없음 — Brain hold_at=verification 5초 동안 앞 frame이 즉시 도착(curl 타임스탬프)
  • 로컬 자원 정리 — -t196 컨테이너 0, 8080·5196·2963x·2967x·2968x·2969x listen 0, Server·testkit·vite·브라우저 프로세스 0
  • 사용량(관찰): solve 10, unsupported_only 0, RUNNING 취소 10 — Server 1.87 순사용량 표와 일치

📸 스크린샷 (실제 로컬 Server·Brain 연결)

A-1 진행 중(검증 전 본문 없음) A-2 Activity 접힘 A-4 같은 Note 두 번째 질문
B-1 진행 중 새로고침 B-2 연결 끊김 → 재연결 중 B-3 재접속 뒤 완료(박스·응답 1개)
C-1 Active Run draft·정지 버튼 C-2 CANCELING "중단 중" C-3 취소 뒤 완료 Activity·draft 유지
D-1 unsupported_only D-2 미검증 공개 D-3 혼합 notice D-4 추가 정보 요청
E-1 409 activeRun 복원(탭 2) E-2 탭 2 완료 F-1 요청 한도 안내

✅ 체크리스트

공통

  • 셀프 리뷰를 완료했습니다
  • 코드 리뷰를 받을 준비가 완료되었습니다
  • pnpm tsc --noEmit 타입 체크를 통과했습니다 — pnpm run build(tsc -b 포함) 로컬 통과, CI 결과 아래
  • 브랜치명 컨벤션을 준수했습니다 (type/이슈번호-작업명) — 오케스트레이션 지정 worktree-task-1.96

인증 관련 변경 시

  • 카카오 / 네이버 로그인 흐름에 영향이 없음을 확인했습니다 — 앱 코드 무변경. 로컬 검증은 발급 token으로만 로그인

📎 기타 참고사항

  • 발견 → 처리: (1) 로컬 Front → Server 브라우저 POST 403(CORS가 https://proovy.ai.kr만 허용) → 게이트 [2] 결정 B로 Server #243에서 해결. (2) dev proxy가 upstream 소켓 단절을 브라우저에 전달하지 않아 SSE가 무한 대기 + Front에 heartbeat 무응답 감지 없음 → [Feat] run SSE 무응답(half-open) 감지 — heartbeat watchdog 재접속 #315. (3) 기본 활성 profile local로 localhost CORS가 열릴 수 있음([Refactor] Spring v2 스트리밍에 맞게 프론트 코드 수정 #243 리뷰) → Server#244.
  • 관찰(이슈 아님): 완료된 run을 새로고침하면 대화 조회 결과에 Activity 박스가 없다(대화 조회 계약에 Activity가 없음, 스펙 Verification은 진행 중 재접속만 요구).
  • 미확인(S14): 실제 유료 모델 응답 품질, Cloud Run·운영 CORS, 공개 배포 E2E(REL-04). Brain 결과 분류는 testkit offline seat 판정이다.
  • 스크린샷·요청 기록·Playwright 하네스는 pr-assets/task-1.96 branch(코드 아님, 73d3d6f — 스크린샷은 2dfacc1).
  • 리뷰 r196 1회차 반영(56e8f0a): R1 요청 원자료·하네스 공개, R2 up 사전 검사·실패 시 로그+정리·Server 조기 종료 감지·/brain/start 본문 보존, R3 STATE_DIR/env·SUFFIX 검증·pkill 경계, R4 Brain warm-up, R5 token 1시간·재발급·auth-storage 예시·KST seed, R6 README git archive+flock Gradle·포트 변경, R7 Server 127.0.0.1 bind, R8 bash -c 제거. 재확인: bash -n, 사전 검사 실패(jar 없음·SUFFIX t-196) 자원 0, 중간 실패(깨진 role-boundary) 로그 출력 뒤 자원 0, 정상 up → 8080은 127.0.0.1만 listen·token /api/users/me 200·Vite proxy 경유 run 완료·status → env로 down 뒤 자원 0.

🤖 Generated with Claude Code

- scripts/local-stack/local-stack.sh: 격리 Postgres·Redis(-t196, 29632·29679), Server(local profile bootJar), proovy-brain testkit(실제 Brain API·graph·store·SSE, offline seat), role 경계, 검증용 사용자·access token 발급, down 정리.
- README: 로컬 stack 실행법, localhost로 접속(Server local CORS는 http://localhost:*만 허용), token 로그인, 요청 한도·scenario 재현, dev proxy 소켓 끊김 한계.
- vite.config.ts: proxy가 브라우저 Origin을 그대로 넘긴다는 주석 정정.

Closes #306

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d, env 저장·SUFFIX 검증·pkill 경계, Brain warm-up, KST seed, README 빌드·token 안내

- up: jar·SQL·포트·컨테이너 사전 검사, ERR trap(errtrace)으로 실패 시 로그 끝부분을 보여 주고 down, Server 기동 중 프로세스 종료를 바로 감지, /brain/start 응답 본문 보존(--fail-with-body), Brain import warm-up(60초 readiness 대비).
- Server SERVER_ADDRESS=127.0.0.1(외부 interface 비노출), ready 안내의 proxy target을 127.0.0.1로.
- up 값을 STATE_DIR/env에 저장해 token·status·down이 읽는다. SUFFIX는 ^[a-z0-9_]+$만, Brain child pkill 패턴에 port 끝 경계.
- seed 사용자 시각을 Asia/Seoul 기준(Server hibernate time_zone)으로.
- README: git archive + flock Gradle bootJar, token 1시간·재발급, auth-storage 최소 예시, 포트 변경·env 공유, 실패 정리, 하네스·요청 기록 위치.

Refs #306

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chowon442
chowon442 merged commit da20a24 into dev Oct 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 실제 Server 연결 텍스트 대화 인수 검증 [1.96]

1 participant