시스템 아키텍처 (C4 Context · Container)
⚠︎ 지난 판 — 2026-07-26. 현재 판은 여기다. 이 문서는 그때의 기록이고 고치지 않는다.
갱신일 2026-07-26 · 기준:
ai@60ee59a·server@bf192cc·web@87e0834이 문서는 무엇이 있고 무엇과 붙어 있는가만 소유한다. 요청이 오가는 순서는 ../interfaces/server-ai.md의 §1 분석(202 + callback)·§2 채팅(SSE passthrough), 스키마는 ../contracts/가 갖는다.
확인한 사실
컨테이너 넷과 외부 여섯
브라우저는 heymoa-ai에 도달할 수단이 없다. web의 생성 API 클라이언트에는 /internal/** 경로가 아예 없고(openapi3.yml에서 제외), ai는 CORS 미들웨어도 인증도 붙이지 않았다(app.py).
경계 다섯
| # | 구간 | 프로토콜 | 인증 | 근거 |
|---|---|---|---|---|
| ① | web → server | REST /v1/** | access_token HttpOnly 쿠키 | CookieBearerTokenResolver |
| ② | web ↔ server | STOMP over WebSocket | 쿠키 (핸드셰이크) | TranscriptionStompConfig |
| ③ | web ← server | SSE (POST 응답이 text/event-stream) | 쿠키 | lib/api/sse.ts |
| ④ | server → ai | REST /internal/v1/** + SSE 소비 | 없음 | agent_chats.py |
| ⑤ | ai → server | REST /internal/v1/** | X-Internal-Token | InternalTokenAuthenticationFilter · context_client.py |
같은 시크릿을 양쪽이 다른 이름의 환경변수로 읽는다 — server는 INTERNAL_API_TOKEN, ai는 INTERNAL_TOKEN이다. 회전할 때 두 이름을 같이 봐야 한다.
컨테이너가 넷인 이유
| 컨테이너 | 왜 따로 있나 | 그래서 갖는 제약 |
|---|---|---|
| web | 브라우저 능력(마이크·AudioWorklet)이 필요하고 화면 수명주기가 서버와 다르다 | 영속 저장소를 갖지 않는다 |
| server | 인증·권한·도메인 상태가 한 곳에 모여야 감사와 잠금이 성립한다 | public edge 전부를 혼자 진다 |
| ai | LLM 호출은 초 단위로 느리고 실패율이 다르다. 같은 프로세스에 두면 요청 스레드를 묶는다 | 202로 끊고 callback으로 되돌려준다 |
| DB 둘 | 파생 데이터가 원본과 같은 database에 있으면 "지워도 되는 것"을 구분할 수 없다 | cross-database 쿼리 금지 (data-architecture.md) |
설계 전제
- server가 유일한 public edge다. 그래서 ai는 인증·CORS를 구현하지 않는다. 이 전제가 깨지면(ai를 외부에 노출하면) ai에 인증 계층을 먼저 만들어야 한다.
- 브로커가 없다. 비동기는 DB row(분석 잡)와 HTTP callback으로 처리한다. 메시지 브로커·gRPC는 병목이 실측될 때 도입한다.
- SSE는 변환 없이 통과시킨다. server가 이벤트를 재해석하지 않기 때문에 web 구간과 ai 구간의 포맷이 같고, 이벤트 정의를 한 곳에만 둘 수 있다. 변환을 시작하는 순간 계약이 둘로 갈라진다.
- 외부 의존은 소유자가 하나씩만 진다. 음성·OAuth는 server, LLM·관측은 ai가 잡는다. 두 서비스가 같은 외부 시스템에 붙으면 크리덴셜도 둘로 늘어난다.
미확인
| 무엇 | 지금 아는 것 | 어디를 봐야 하나 |
|---|---|---|
| ④의 네트워크 격리 수단 | 코드에는 인증이 없고, 계약이 이를 미결로 등재했다 | 실제 보안그룹·네트워크 구성 (aws-architecture.md) |
| ai의 운영 주소 | server application.yml의 기본값은 http://localhost:8000이고 prod 오버라이드가 저장소에 없다 | EC2의 .env.prod |
| 분석 완료 push 채널 | 설계 스펙은 STOMP push를 적었지만 server 코드의 STOMP 발행은 전사 이벤트뿐이고 web은 폴링한다 | pm/2-product/open-issues.md 등재 대상 |