본문으로 건너뛰기

API 계약 registry

갱신일 2026-08-09 · 기준: ../contracts/ 5개 파일 + ai@60ee59a · server@1fa7a6a · web contract@398c38e 여기가 MVP2 계약 registry다. MVP1/interfaces/api-registry.md는 MVP1 종료 시점 기록으로 동결됐다 — 소급 수정하지 않는다. APP-148 — 초대가 이메일 중심으로 넓어졌다. POST /v1/invitations/accept-by-token이 늘어 경로 37 → 38, operation·schema는 48 → 49다. 초대 상태에 EXPIRED가, 초대 목록·알림에 expiresAt이 붙었고 inviteeName이 nullable이 됐다(미가입자 초대). INVITEE_NOT_FOUND는 폐기됐다 — 미가입자 초대가 정상 경로가 됐기 때문이다. APP-343 — 노트 응답의 meetingStartedByemail·image가 붙었다. 경로·operation·schema 수는 그대로다. 시작자와 participants[]가 이제 같은 네 필드를 쓴다 — 예전에는 시작자만 userId·name이라 web이 아바타 이미지를 참여자 목록에서 빌려 썼고, 시작자가 참여자가 아닌 회의에서는 빌릴 데가 없었다. APP-379 — 워크스페이스 멤버 관리가 붙었다. PATCH /v1/workspaces/{workspaceId}/members/{userId} (역할 변경) · DELETE .../members/{userId}(추방) · DELETE .../members/me(나가기)가 늘어 경로 38 → 40, operation 49 → 52, schema 49 → 50(ChangeWorkspaceMemberRoleRequest)이다. 셋 다 204 무본문이고 새 오류 코드가 둘 붙었다 — WORKSPACE_MEMBER_NOT_FOUND(404, 워크스페이스는 있고 대상만 멤버가 아님) · LAST_WORKSPACE_ADMIN(409, ADMIN이 최소 한 명 남아야 함). OWNER 역할은 만들지 않았다 — 소유권 이전은 "상대를 ADMIN으로 올리고 내가 나간다" 두 호출이다. 계약 파일을 고치기 전에 이 표부터 본다. 미러를 손으로 고치면 다음 생성에서 덮인다. ⚠️ 생성은 ./gradlew clean openapi3이다. clean을 빼면 build/generated-snippets/의 옛 스니펫이 섞여 삭제된 경로가 계약에 되살아난다 — APP-214 작업 중 실제로 meeting-pause·meeting-resume가 부활한 산출물이 나왔다. 복사 전에 경로 수를 센다(현재 39). 스키마 본문은 여기 없다 — 아래 링크가 정본이다.


확인한 사실

1. 파일 전체 — owner · source · status

파일경계ownersource (원본이 어디)status규모
openapi3-server.ymlweb → server public REST + ai → server internal RESTheymoa-server빌드 생성물restdocs-api-specXxxControllerDocsTest에서 생성🟢 구현 반영됨39경로 (public 36 / internal 3) · 51 operation · 50 schema · OpenAPI 3.0.1 · info.version 1.0.0
asyncapi-web-server.ymlweb ↔ server 비동기 (전사·노트 토픽 STOMP + 채팅 SSE)합본 — 구간마다 다름전사·노트 토픽 STOMP = heymoa-server/asyncapi.yml 미러 / 채팅 SSE = 이 파일이 원본🟢 구현 반영됨8채널 · 8 operation (STOMP 6 / SSE 2) · 2서버 · AsyncAPI 3.0.0 · info.version 2.1.0
asyncapi-server-ai.ymlserver ↔ ai 채팅 SSEheymoa-ai계획 문서 — 이벤트 스키마는 web-server 파일을 $ref🟡 계획 · 구현이 따라옴1채널 · 1 operation · 이벤트 8종 전부 $ref · AsyncAPI 3.0.0 · info.version 1.1.0
openapi3-ai.ymlserver → ai 내부 REST + 분석 결과 callbackheymoa-ai수기 원본 — 계약이 먼저, 구현이 따라온다🟡 계획 · 구현이 따라옴3경로 · 3 operation + callback 1 · OpenAPI 3.1.0 · info.version 1.1.0
agent-chat-flow.mdweb → server → ai 채팅 루프공동수기 서술 — 계약이 아니라 흐름·경계 규칙 요약🟢 구현 반영됨시퀀스 3종 + 경계 규칙 표
heymoa-web/openapi3.yml (구현 레포)web이 코드 생성에 쓰는 사본heymoa-web미러openapi3-server.yml에서 /internal/** 3경로·3 operation과 전용 schema·의존성 제거🟢 APP-401 반영됨36경로 · 48 operation · 43 schema

asyncapi-web-server.yml이 합본인 것이 이 registry에서 가장 헷갈리는 지점이다. 같은 파일 안에서 전사·노트 토픽 STOMP 구간은 미러(고치면 덮인다), 채팅 SSE 구간은 원본(여기서 고쳐야 한다)이다. 어느 구간을 만지는지 먼저 확인한다.

회의 상태의 owner는 server다. 목록과 상세의 meetingStatus·meetingStartedAt· recordedDurationMs·activeSessionStartedAt이 같은 조회 시점 snapshot이며, meetingStatusNOT_STARTED·IN_PROGRESS·PAUSED·ENDED 네 값이다. web은 기존 recording.started·recording.stopped를 받으면 이벤트 payload로 상태나 시간을 만들지 않고, 현재 노트 상세 query와 캐시된 프로젝트 노트 목록 query를 invalidate한다 (web@fad1d73, recording.stopped는 전사 query도 invalidate). 활성 query만 즉시 refetch하고 비활성 목록은 다음 사용 시 refetch한다.

2. 동기화 방향

흐름을 한 줄로: server 코드 → 생성 → contracts → web 사본 → web 생성 클라이언트. 이 사슬 중간을 손으로 고치면 다음 생성에서 사라진다.

3. 무엇이 바뀌면 무엇을 하나

바뀐 것고칠 곳그다음잊으면
server의 REST 시그니처·DTOserver 코드 (계약 아님)./gradlew clean openapi3openapi3-server.yml 복사 → web 사본 갱신 → pnpm orvalweb 생성 훅이 낡은 채로 남는다
전사·노트 토픽 STOMP 메시지·목적지heymoa-server/asyncapi.yml (원본)*WebSocketDocsTest 통과 → asyncapi-web-server.yml의 STOMP 구간에 복사미러가 갈라진다
agent 채팅 SSE 이벤트asyncapi-web-server.yml (원본)끝. asyncapi-server-ai.yml$ref하므로 자동 반영두 구간 포맷이 갈라져 passthrough가 깨진다
ai 내부 REST 요청·응답openapi3-ai.yml (수기 원본)ai 구현이 따라간다계약과 구현이 갈라진다
채팅 흐름의 경계 규칙agent-chat-flow.md필요하면 interfaces/의 해당 문서도규칙이 구두 전승으로 남는다
/internal 경로 추가server 코드openapi3-server.yml 자동 반영 → web 사본에서 반드시 제거web 생성물에 내부 경로가 섞인다

계약을 바꿨으면 관련 repo PR과 Linear에 그 commit SHA를 남긴다.

4. 검증

파일검증 방법자동인가
openapi3-server.yml생성 자체가 검증 — 코드에서 나오므로 코드와 어긋날 수 없다. ExposedEnumContractTest가 외부 노출 enum 값 집합을 고정✅ server CI (build)
asyncapi-web-server.yml 전사·노트 토픽 구간heymoa-server*WebSocketDocsTest실제 DTO 직렬화로 원본을 검증✅ server CI
asyncapi-web-server.yml 채팅 SSE 구간스펙 문법 검증만npx --yes @asyncapi/cli validate. server가 passthrough라 자기 DTO가 없어 DocsTest로 검증할 수 없다⚠️ 수동
asyncapi-server-ai.yml문법 검증 + $ref 해석⚠️ 수동
openapi3-ai.yml문법 검증만. 수기 원본이라 구현과의 일치는 사람이 지킨다⚠️ 수동
heymoa-web/openapi3.ymlpnpm orval이 통과해야 하고, "내부 경로 클라이언트를 만들지 않는다"를 고정하는 테스트가 web에 있다✅ web 로컬 (CI 없음)
npx --yes @asyncapi/cli validate <파일> # asyncapi 3종

검증 강도가 파일마다 다르다. 생성물 두 개(REST·전사 STOMP)는 코드가 곧 검증이고, 채팅 SSE 3파일은 문법 검증뿐이다. 이 구간이 계약과 구현이 갈라질 수 있는 유일한 지점이며, 실제로 §미결 1이 그 사례다.

5. 소비자 — 누가 이 파일을 읽나

파일heymoa-webheymoa-serverheymoa-ai
openapi3-server.yml코드 생성(사본 경유) — 훅·모델·MSW·faker생성 주체내부 조회 API 시그니처 확인 (InternalAgentContext)
asyncapi-web-server.yml수기 zod 프로토콜·MSW 시나리오 구현전사 구간 원본 소유 · SSE 구간 passthrough이벤트 스키마의 실질 정본 ($ref 대상)
asyncapi-server-ai.yml스트림 소비 규약 확인SSE 생산 규약
openapi3-ai.yml호출 시그니처·타임아웃구현 대상
agent-chat-flow.md승인 UX·게이트 판정tee·락·재검증 규칙profile 선택·interrupt 규칙

web은 어떤 계약도 원본으로 소유하지 않는다 — 전부 소비자다. 그래서 web에서 계약을 고치고 싶을 때는 항상 상류(server 코드 또는 asyncapi-web-server.yml)로 올라가야 한다.


미결

  1. chatKind가 계약에는 있고 구현에는 없다. openapi3-ai.yml이 이 필드로 agent profile이 갈린다고 규정하면서, 같은 파일에 "현재 heymoa-server는 이 필드를 보내지 않는다(요청 DTO에 없다)"고 적어 두었다. 계약 파일이 스스로 미구현을 기록한 유일한 사례이며 status를 🟡로 둔 이유다. APP-172로 MVP2 이월 — 원장: pm/2-product/open-issues.md.
  2. agent-chat-flow.md 안에 표기가 엇갈리는 곳이 둘 있다. ① tee 개수 — §1 시퀀스는 tee 1/2·tee 2/2인데 §3 경계 규칙 표는 (USER·ASSISTANT·TOOL)이다. ② keepalive 간격 — "스트림 수명" 행은 30초 이하인데 "heartbeat" 행과 asyncapi-server-ai.yml15초 이하다. interfaces/ 문서들은 각각 15초를 따랐다. 계약 문서 쪽 정정이 필요하다.
  3. 채팅 SSE 구간에 계약↔구현 자동 검증이 없다. passthrough 구조상 server에 DTO가 없어 생성·검증 모두 불가능하다. 세 서비스 중 어느 쪽이 이 구간의 회귀를 막을지 정해져 있지 않다.
  4. /internal 제거가 수작업이다. web 사본을 만들 때 3경로를 손으로 지운다. 자동화 지점이 지정돼 있지 않아 경로가 늘면 누락될 수 있다 — web에 이를 고정하는 테스트가 있지만 사후 탐지이지 예방은 아니다.
  5. asyncapi-server-ai.yml의 상대 $ref가 파일 배치에 묶여 있다. 같은 폴더의 asyncapi-web-server.yml을 파일명으로 참조하므로 둘을 떼어 놓으면 해석이 깨진다. 이관 시 함께 옮겨야 한다.

이어서 볼 것어디
계약 폴더의 파일 목록·성격../contracts/INDEX.md · ../contracts/README.md
세 서비스 공통 규약common-conventions.md
경계별 상세server-web.md · server-ai.md
계약 설계의 근거 (3-서비스 구조)MVP1/architecture/system-architecture.md