nomadcode/packages/contracts/notes/flutter-core-api-candidates.md

367 lines
18 KiB
Markdown

# Flutter/Core API Contract Candidates
이 문서는 source schema가 아니라 Flutter-first 전환 중 공유 계약 후보를 모으는 compatibility note다.
실제 Go core 구현(`services/core`)과 향후 작성될 Flutter 클라이언트(`apps/client`) 간의 인터페이스 정합성을 보장하기 위한 임시 계약 후보이며, 단일 source-of-truth 스키마가 정의되기 전까지의 과도기적 스펙을 정의한다.
## API 안정성 레벨 정의
- **안정 (Stable)**: 인터페이스와 데이터 포맷이 고정되어 있어 즉시 연동에 사용할 수 있음.
- **후보 (Candidate)**: 연동 후보 스펙으로, 실연동 과정에서 필드가 추가되거나 변경될 수 있음.
---
## 1. 내부 통신 원칙
### 1.1 기본 표준
- **설명**: NomadCode 내부 런타임 간 신규 통신은 `proto-socket`을 기본 표준으로 사용한다.
- **안정성 수준**: 후보 (Candidate)
- **적용 대상**:
- `apps/client``services/core`
- NomadCode Core ↔ 내부 control/runtime adapter
- 향후 NomadCode가 직접 소유하는 MCP/action runtime 표면
- **예외 대상**:
- health/readiness처럼 외부 운영 도구와 curl smoke가 직접 확인해야 하는 endpoint
- Plane/Jira/Mattermost/IOP/OpenAI-compatible Responses/A2A처럼 외부 provider 또는 외부 실행 표면이 요구하는 native protocol
- public callback/webhook
- 단순 정적 조회처럼 `proto-socket` 연결 비용이 과한 read-only endpoint
- **예외 기록 규칙**: REST/HTTP를 새로 추가하거나 유지할 때는 이 문서 또는 관련 domain rule에 예외 사유를 남긴다.
### 1.2 proto-socket Semantic Envelope 후보
- **설명**: 아래 JSON은 wire schema가 아니라 proto-socket message가 가져야 할 의미 필드 후보다. 실제 source schema는 proto-socket 계약이 안정화되면 별도 schema로 승격한다.
- **Envelope Shape 후보**:
```json
{
"protocol_version": "nomadcode.proto-socket.v1",
"id": "msg-uuid",
"correlation_id": "request-or-flow-id",
"type": "request",
"channel": "task",
"action": "task.list",
"actor": {
"kind": "client",
"id": "apps/client"
},
"auth": {
"scheme": "basic",
"token_ref": "runtime-secret-ref"
},
"payload": {},
"error": null,
"meta": {
"connection_id": "conn-id",
"timestamp": "2026-05-30T00:00:00Z"
}
}
```
- **type 후보**:
- `request`: 요청 메시지
- `response`: 요청 성공 응답
- `event`: 구독 또는 서버 발행 이벤트
- `error`: 요청 실패 또는 channel-level 오류
- `heartbeat`: 연결 생존 확인
- `auth`: 연결 인증 또는 인증 갱신
- **error 후보 필드**:
```json
{
"code": "task.not_found",
"message": "task not found",
"retryable": false,
"details": {}
}
```
### 1.3 REST Compatibility Map 후보
- **설명**: 현재 REST API는 proto-socket rail 구현 전까지의 호환 표면이다. task 관련 신규 내부 기능은 proto-socket channel을 우선한다.
| 현재 REST/HTTP 표면 | proto-socket 후보 | 유지/전환 기준 |
|--------------------|-------------------|----------------|
| `GET /healthz` | 없음 | Public 운영 health check로 REST 유지 |
| `GET /readyz` | `system.ready` event 또는 request 후보 | DB readiness와 curl smoke 때문에 REST 유지, 내부 UI에는 proto-socket 상태 이벤트를 추가 가능 |
| `POST /api/tasks` | `task.create` request | client-core 내부 호출은 proto-socket으로 전환, REST는 smoke/compat 유지 |
| `GET /api/tasks` | `task.list` request | client-core 내부 조회는 proto-socket으로 전환, REST는 smoke/compat 유지 |
| `GET /api/tasks/{id}` | `task.get` request | client-core 내부 조회는 proto-socket으로 전환, REST는 smoke/compat 유지 |
| `POST /api/tasks/{id}/enqueue` | `task.enqueue` request + `task.status.changed` event | client-core 실행 요청과 상태 반영은 proto-socket으로 전환, REST는 smoke/compat 유지 |
| `POST /api/integrations/plane/tasks` | `workitem.plane.import` 후보 | Plane API 자체는 외부 provider REST이며, 내부 호출 표면은 proto-socket으로 추가 가능 |
### 1.4 Core Endpoint 후보
- **기본 경로**: `/proto-socket`
- **인증**: 기존 Core Basic Auth middleware 적용
- **Wire message**: 초기 구현은 `google.protobuf.Struct` semantic envelope를 사용한다.
- **Heartbeat**: proto-socket 기본 heartbeat를 사용하며 Core 설정 기본값은 interval 30s, wait 10s다.
### 1.5 Task Channel Actions (구현됨)
- **설명**: `services/core/internal/protosocket`가 REST task API와 같은 의미를 `task` channel의 proto-socket action으로 제공한다. REST는 smoke/compat로 유지한다.
- **안정성 수준**: 후보 (Candidate)
- **채널**: `task`
- **요청/응답 action 표**:
| action | request payload | response type | response payload |
|--------|-----------------|---------------|------------------|
| `task.create` | `POST /api/tasks` JSON shape (`title`, `source`, `payload`, `metadata?`, `external?`) | `response` | `{ "id", "status", "external_provider", "external_id", "task": { …full task } }` |
| `task.list` | `{ "limit": 20 }` (생략 시 0 전달, 기본 20/최대 100 bounding은 core workflow service가 담당) | `response` | `{ "tasks": [ { …full task } ] }` |
| `task.get` | `{ "id": "task-id" }` | `response` | `{ "task": { …full task } }` |
| `task.enqueue` | `{ "id": "task-id" }` | `response` | `{ "id", "status": "queued", "task": { …full task } }` |
- **task status event (서버 발행)**:
| action | type | channel | payload |
|--------|------|---------|---------|
| `task.status.changed` | `event` | `task` | `{ "id", "type": "task.running\|task.completed\|task.failed\|task.canceled", "status", "title", "message" }` |
- scheduler가 내는 모든 task lifecycle event(running/completed/failed/canceled)가 notification fanout을 통해 broadcast된다. completed event의 Mattermost 알림 동작은 그대로 유지된다.
- **error envelope code 표** (`type: "error"`):
| code | 의미 | retryable | REST 대응 status |
|------|------|-----------|------------------|
| `task.invalid_input` | 잘못된 task 입력(`ErrInvalidTaskInput`) | false | 400 |
| `task.conflict` | 현재 상태에서 enqueue 불가(`ErrTaskCannotBeEnqueued`) | false | 409 |
| `task.not_found` | task 없음(`pgx.ErrNoRows`) | false | 404 |
| `task.invalid_payload` | envelope payload 디코드 실패 | false | (REST 없음) |
| `internal.error` | 그 외 내부 오류 | true | 500 |
| `UNSUPPORTED_ACTION` | dispatcher에 등록되지 않은 action | false | (REST 없음) |
### 1.6 Diagnostics 필드 후보
- **설명**: proto-socket 로그와 클라이언트 디버그 표면에서 연결과 메시지 흐름을 추적하기 위한 공통 diagnostics 필드 후보다. 확정 schema가 아니라 Core/Client 구현을 맞추기 위한 compatibility note로만 사용한다.
- **안정성 수준**: 후보 (Candidate)
- **최소 공통 필드**:
| 필드 | 위치 | 필수성 | 소비자 책임 |
|------|------|--------|-------------|
| `connection_id` | envelope `meta.connection_id`, Core log attribute, Client debug surface | 연결이 확립된 response/event/error에서 가능하면 포함 | Core는 연결별 안정 식별자를 부여하고, Client는 표시/필터링에만 사용한다. |
| `protocol_version` | envelope top-level, Core log attribute, Client debug surface | 모든 proto-socket envelope에 포함 | Core와 Client는 지원하지 않는 version을 compatibility 문제로 보고하되, secret이나 payload 원문을 함께 출력하지 않는다. |
| `channel` | envelope top-level, Core log attribute, Client debug surface | request/response/event/error에서 포함 | dispatcher와 Client debug surface는 channel name을 그대로 노출하되 business payload를 함께 노출하지 않는다. |
| `action` | envelope top-level, Core log attribute, Client debug surface | request/response/event/error에서 포함 | request 흐름과 최근 event/error를 추적하는 용도로만 사용한다. |
| `error.code` / `error_code` | envelope `error.code`, Core log attribute, Client debug surface | error envelope에서 포함, 성공 envelope에서는 생략 또는 null | Core는 error code를 안정적인 문자열로 기록하고, Client는 최근 오류 요약에만 사용한다. |
| `timestamp` | envelope `meta.timestamp`, Core log timestamp, Client debug surface | Core가 발행하는 response/event/error에서 가능하면 포함 | 소비자는 시간 정렬과 최근 상태 표시에 사용하고 clock 차이를 프로토콜 오류로 단정하지 않는다. |
- **Core log attributes**: `connection_id`, `protocol_version`, `channel`, `action`, `error_code`, `timestamp`를 structured log attribute로 남긴다. error가 없으면 `error_code`는 생략하거나 null로 둔다.
- **Envelope meta**: response/event/error는 가능한 경우 `meta.connection_id`, `meta.timestamp`를 포함하고, top-level `protocol_version`, `channel`, `action`과 함께 diagnostics 기준으로 소비된다.
- **Client debug surface**: 연결 상태, connection id, protocol version, 최근 channel/action/error code/timestamp만 표시한다.
- **노출 금지**: auth token, Basic Auth header, secret, `token_ref`가 가리키는 실제 값, payload/result 원문, 외부 provider credential은 Core log와 Client debug surface에 기록하거나 표시하지 않는다.
### 1.7 새 내부 통신 추가 운영 체크
- 신규 `apps/client``services/core` 또는 내부 runtime 간 통신은 먼저 proto-socket channel/action으로 표현 가능한지 확인한다.
- REST/HTTP를 새로 추가하거나 유지해야 하면 health/readiness, 외부 provider 호환 API, 운영 smoke/curl, public callback, 단순 정적 조회 중 어느 예외인지 이 문서 또는 관련 domain rule에 기록한다.
- 새 channel/action은 envelope의 `protocol_version`, `type`, `channel`, `action`, `correlation_id`, `payload`, `error.retryable` 의미를 이 문서에 추가한다.
- 클라이언트가 소비하는 request/response/event shape는 구현 전에 후보 표와 테스트 fixture 기준을 남긴다.
- Core 구현은 기존 REST smoke/compat 표면을 제거하지 않고, 내부 UI/런타임 호출만 proto-socket 우선 경로로 전환한다.
- 검증은 최소한 channel/action dispatch, auth boundary, error envelope, event broadcast 또는 reconnect 영향 중 해당되는 항목을 테스트로 고정한다.
---
## 2. 공통 및 인프라 API
### 2.1 `GET /healthz`
- **설명**: 서비스 헬스 체크.
- **인증**: 없음 (Public)
- **안정성 수준**: 안정 (Stable)
- **Response Shape** (Status 200 OK):
```json
{
"status": "ok"
}
```
### 2.2 `GET /readyz`
- **설명**: 서비스 및 데이터베이스 준비 상태 체크.
- **인증**: Basic Auth
- **안정성 수준**: 안정 (Stable)
- **Response Shape** (Status 200 OK):
```json
{
"status": "ready"
}
```
*(Status 503 Service Unavailable)*:
```json
{
"error": "database is not ready"
}
```
---
## 3. 태스크 관리 API (Tasks API)
### 3.1 `POST /api/tasks`
- **설명**: 새로운 태스크 생성.
- **인증**: Basic Auth
- **안정성 수준**: 안정 (Stable)
- **Request Shape**:
```json
{
"title": "Task Title",
"source": "source_identifier",
"payload": {},
"metadata": {},
"external": {
"provider": "plane",
"id": "external-task-id",
"url": "https://...",
"metadata": {}
}
}
```
- **Response Shape** (Status 201 Created):
```json
{
"id": "task-uuid-string",
"status": "pending",
"external_provider": "plane",
"external_id": "external-task-id"
}
```
### 3.2 `GET /api/tasks`
- **설명**: 태스크 목록 조회.
- **인증**: Basic Auth
- **안정성 수준**: 안정 (Stable)
- **Query Parameters**:
- `limit` (Optional, 기본값 20, 양의 정수)
- **Response Shape** (Status 200 OK):
```json
[
{
"id": "task-uuid-string",
"title": "Task Title",
"source": "source_identifier",
"status": "pending",
"payload": {},
"result": {},
"error": null,
"created_at": "2026-05-24T12:00:00Z",
"updated_at": "2026-05-24T12:00:00Z",
"external_provider": "plane",
"external_id": "external-task-id",
"external_url": "https://...",
"external_metadata": {},
"metadata": {}
}
]
```
### 3.3 `GET /api/tasks/{id}`
- **설명**: 단일 태스크 세부 정보 조회.
- **인증**: Basic Auth
- **안정성 수준**: 안정 (Stable)
- **Response Shape** (Status 200 OK):
```json
{
"id": "task-uuid-string",
"title": "Task Title",
"source": "source_identifier",
"status": "pending",
"payload": {},
"result": {},
"error": null,
"created_at": "2026-05-24T12:00:00Z",
"updated_at": "2026-05-24T12:00:00Z",
"external_provider": "plane",
"external_id": "external-task-id",
"external_url": "https://...",
"external_metadata": {},
"metadata": {}
}
```
### 3.4 `POST /api/tasks/{id}/enqueue`
- **설명**: 태스크를 큐에 진입시켜 실행 대기 상태로 변경.
- **인증**: Basic Auth
- **안정성 수준**: 안정 (Stable)
- **Response Shape** (Status 200 OK):
```json
{
"id": "task-uuid-string",
"status": "queued"
}
```
---
## 4. 외부 연동 API (Integrations API)
### 4.1 `POST /api/integrations/plane/tasks`
- **설명**: Plane 워크아이템 정보를 기반으로 새로운 태스크 생성.
- **인증**: Basic Auth
- **안정성 수준**: 안정 (Stable)
- **Request Shape**:
```json
{
"workspace_slug": "my-workspace",
"project_id": "project-uuid-string",
"work_item_id": "work-item-id-string",
"state_id": "state-uuid-string",
"external_url": "https://...",
"comment": "Optional comment"
}
```
- **Response Shape** (Status 201 Created):
```json
{
"id": "task-uuid-string",
"status": "pending",
"external_provider": "plane",
"external_id": "work-item-id-string"
}
```
---
## 5. Workspace / Project Metadata 계약 후보
> [!IMPORTANT]
> **Workspace/Project Metadata 상태**: 현재 Go core 백엔드에 이와 관련한 별도의 Source-of-Truth 저장소나 전용 테이블 구조가 존재하지 않으며, API 명세 또한 완전히 확정되지 않았습니다. 아래는 Flutter 앱과 코어 간 향후 연동을 대비한 **계약 후보 필드 목록**입니다.
### 5.1 Workspace Metadata 후보 스펙
- **설명**: 에이전트 작업 공간(Workspace)에 대한 설정 및 상태 메타데이터.
- **후보 필드**:
- `workspace_id`: 작업 공간 고유 식별자 (UUID)
- `workspace_slug`: 작업 공간의 URL 친화적 식별자 (예: `nomadcode-dev`)
- `name`: 작업 공간 표시 이름 (예: `NomadCode Dev Workspace`)
- `owner_id`: 소유자 고유 식별자
- `created_at`: 생성 시각 (ISO 8601)
- `updated_at`: 최종 변경 시각 (ISO 8601)
- `status`: 현재 상태 (예: `active`, `archived`, `suspended`)
- `settings`: 작업 공간에 특화된 동적 설정 JSON 오브젝트 (예: 자동 스케줄링 옵션, 알림 채널 정보 등)
### 5.2 Project Metadata 후보 스펙
- **설명**: 작업 공간 내 세부 프로젝트(Project) 정보.
- **후보 필드**:
- `project_id`: 프로젝트 고유 식별자 (UUID)
- `workspace_id`: 해당 프로젝트가 속한 Workspace 식별자
- `name`: 프로젝트 표시 이름 (예: `Flutter client consolidation`)
- `description`: 프로젝트 세부 설명
- `provider`: 연동 플랫폼 식별자 (예: `plane`, `github`)
- `external_project_id`: 연동 플랫폼 측 프로젝트 ID
- `status`: 프로젝트 상태 (예: `active`, `paused`)
---
## 6. 클라이언트 Integration 설정 계약 후보
> [!IMPORTANT]
> 이 절은 `apps/client`의 integration boundary에서 host/plugin/transport 사이에 교환되는 설정값을 후보로 모은 것이다. 실제 source schema는 소비하는 core/client 경계가 안정화된 뒤로 미룬다.
> 관련 코드 경계: `apps/client/lib/src/integrations/proto_socket/` 와 `apps/client/lib/src/integrations/mattermost/`.
### 6.1 proto-socket Endpoint 설정 후보
- **설명**: `apps/client`가 proto-socket transport에 의존할 때 사용하는 연결/하트비트 설정. 현재 구현은 `proto_socket` Dart 패키지(local path)를 끌어 쓰며 bootstrap에서 자동 연결하지 않는다.
- **후보 필드**:
- `host`: 대상 서버 hostname
- `port`: TCP/WebSocket 포트 (기본은 `secure` 값에 따라 443 또는 80)
- `secure`: TLS 사용 여부 (boolean)
- `path`: WebSocket path (기본 `/`)
- `heartbeat_interval_seconds`: heartbeat 송신 주기 (기본 30)
- `heartbeat_wait_seconds`: heartbeat 응답 대기 한계 (기본 60)
- `enabled`: bootstrap 시 자동 연결 활성화 여부 (기본 false)
### 6.2 Mattermost Push Host 책임 경계 후보
- **설명**: Mattermost push 통합에서 `apps/client` host가 plugin adapter에게 위임/공급해야 하는 데이터와 콜백. plugin adapter는 platform 측 plugin singleton에 격리되어 있고, 그 외 코드는 모두 `MattermostPushClient` 인터페이스에만 의존한다.
- **호스트 소유 필드/콜백**:
- `server_url`: Mattermost 서버 base URL (auth 핸드오프, signing key 저장에 함께 사용)
- `server_identifier`: 선택 필드. FCM payload의 `server_id``server_url`로 역해석해야 하는 multi-server host에서 plugin의 `setAuthToken(..., identifier:)`로 함께 전달
- `auth_token`: 로그인 응답에서 받은 인증 토큰 (plugin의 `setAuthToken`으로 전달)
- `signing_key`: 서버 config의 `AsymmetricSigningPublicKey` (plugin의 `setSigningKey`로 전달)
- `device_token`: FCM 디바이스 토큰 (plugin의 `getDeviceToken``onDeviceTokenReady` 콜백으로 동기화)
- `on_navigate_to_channel(server_url, channel_id)`: notification open → 채널 라우팅 콜백
- `on_navigate_to_thread(server_url, root_id)`: notification open → CRT 스레드 라우팅 콜백
- `notification_stream`: app-level UI(snackbar 등) 소비를 위한 broadcast stream