<!-- task=m-proto-socket-infrastructure-communication-rail/02+01_task_service plan=0 tag=API -->

# Plan - API Proto-Socket Task Service

## 이 파일을 읽는 구현 에이전트에게

이 plan의 구현이 끝나면 active `CODE_REVIEW-*-G??.md`에서 구현 에이전트 소유 섹션을 실제 구현 내용, 검증 출력, 계획 대비 변경 사항으로 채워야 한다. 최종 archive, `complete.log`, 리뷰 판정은 code-review-skill 전용이다. 구현 중 사용자만 결정할 수 있는 범위 변경, 사용자 소유 외부 환경/secret 준비, 또는 안전한 진행을 막는 scope 충돌이 있으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정과 근거를 남기고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 메울 수 있는 검증 공백만으로는 사용자 리뷰 요청을 만들지 않는다.

## 배경

Core task channel은 `task.create`, `task.list`, `task.get`, `task.enqueue`, `task.status.changed`를 proto-socket envelope로 제공한다. Flutter client는 transport가 생긴 뒤에도 task 도메인 모델과 request parsing 경계가 없으면 화면에서 mock data를 대체할 수 없다. 이 작업은 UI를 바꾸지 않고 client service 경계만 만든다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. code-review가 해당 요청을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/workflow-core/PHASE.md`
- `agent-roadmap/phase/workflow-core/milestones/proto-socket-infrastructure-communication-rail.md`
- `agent-ops/rules/project/domain/mobile/rules.md`
- `agent-test/local/mobile-smoke.md`
- `apps/client/lib/src/integrations/proto_socket/proto_socket_lifecycle.dart`
- `apps/client/lib/src/features/workspaces/domain/project_workspace.dart`
- `apps/client/lib/src/features/workspaces/presentation/workspace_home_page.dart`
- `apps/client/test/widget_test.dart`
- `packages/contracts/notes/flutter-core-api-candidates.md`
- `services/core/internal/protosocket/tasks.go`

### 테스트 커버리지 공백

- No existing Flutter test covers task channel request creation.
- No existing Flutter test covers Core error envelope mapping to a client exception.
- Widget tests use `mockProjectWorkspaces` only; they do not exercise task service consumption.

### 심볼 참조

- No renamed or removed symbols planned.
- New service should depend on the `ProtoSocketTransport` produced by `01_client_transport`.

### 분할 판단

Split policy was evaluated before writing files. This subtask depends on `01_client_transport` because it needs `ProtoSocketEnvelope` and `ProtoSocketTransport`. UI integration remains in `03+02_workspace_integration` so service parsing can be reviewed independently.

### 범위 결정 근거

This task does not change `WorkspaceHomePage` rendering or app bootstrap. It does not create Core APIs, new REST endpoints, or persistence. It only creates a Flutter task channel client and tests using fake transport envelopes.

### 빌드 등급

Build lane `cloud-G06`, review lane `cloud-G06`: protocol response parsing and error mapping with moderate blast radius, but no UI or platform runner changes.

## 구현 체크리스트

- [ ] [API-1] Add client task models matching the implemented Core task channel response shape.
- [ ] [API-2] Add `ProtoSocketTaskService` that calls `task.list`, `task.get`, `task.enqueue`, and optional `task.create` through `ProtoSocketTransport`.
- [ ] [API-3] Add task channel service tests for success, missing payload, and error envelope mapping.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [API-1] Task Models

### 문제

The workspace domain model only has `ProjectWorkspace` and a mock count at `apps/client/lib/src/features/workspaces/domain/project_workspace.dart:3-18`. The Core task channel returns full task maps described in `packages/contracts/notes/flutter-core-api-candidates.md:97-108`.

### 해결 방법

Add `apps/client/lib/src/features/workspaces/domain/workspace_task.dart`.

Before (`apps/client/lib/src/features/workspaces/domain/project_workspace.dart:3-18`):

```dart
class ProjectWorkspace {
  final String id;
  final String name;
  final String description;
  final ProjectStatus status;
  final String codeServerUrl;
  final int tasksCount;
}
```

After:

```dart
class WorkspaceTask {
  final String id;
  final String title;
  final String status;
  final String source;
  final String? error;

  const WorkspaceTask({
    required this.id,
    required this.title,
    required this.status,
    required this.source,
    this.error,
  });

  factory WorkspaceTask.fromMap(Map<String, Object?> map) {
    return WorkspaceTask(
      id: map['id'] as String? ?? '',
      title: map['title'] as String? ?? 'Untitled task',
      status: map['status'] as String? ?? 'unknown',
      source: map['source'] as String? ?? '',
      error: map['error'] as String?,
    );
  }
}
```

### 수정 파일 및 체크리스트

- [ ] Add `apps/client/lib/src/features/workspaces/domain/workspace_task.dart`.
- [ ] Keep model tolerant of missing optional fields returned by partial task maps.

### 테스트 작성

Cover `WorkspaceTask.fromMap` in the service test file, using a full map and a minimal map.

### 중간 검증

```bash
cd apps/client && flutter test test/integrations/proto_socket_task_service_test.dart
```

Expected: task model tests pass.

## [API-2] Task Channel Service

### 문제

Core registers task actions at `services/core/internal/protosocket/tasks.go:32-36`, but Flutter has no client API for sending those actions through the transport.

### 해결 방법

Add `apps/client/lib/src/integrations/proto_socket/proto_socket_task_service.dart`.

Before (`services/core/internal/protosocket/tasks.go:32-36`):

```go
dispatcher.Register("task.create", c.handleCreate)
dispatcher.Register("task.list", c.handleList)
dispatcher.Register("task.get", c.handleGet)
dispatcher.Register("task.enqueue", c.handleEnqueue)
```

After:

```dart
class ProtoSocketTaskService {
  final ProtoSocketTransport _transport;

  ProtoSocketTaskService(this._transport);

  Future<List<WorkspaceTask>> listTasks({int limit = 20}) async {
    final response = await _transport.sendRequest(
      ProtoSocketEnvelope(
        id: _newMessageId(),
        type: 'request',
        channel: 'task',
        action: 'task.list',
        payload: {'limit': limit},
      ),
    );
    _throwIfError(response);
    final rawTasks = response.payload['tasks'];
    if (rawTasks is! List) return const [];
    return rawTasks
        .whereType<Map>()
        .map((item) => WorkspaceTask.fromMap(Map<String, Object?>.from(item)))
        .toList(growable: false);
  }
}
```

Also add `getTask`, `enqueueTask`, and `createTask` only if their signatures stay thin wrappers around the implemented Core actions. Do not invent workspace/project metadata APIs.

### 수정 파일 및 체크리스트

- [ ] Add `apps/client/lib/src/integrations/proto_socket/proto_socket_task_service.dart`.
- [ ] Add `ProtoSocketTaskException` with `code`, `message`, and `retryable`.
- [ ] Use the Core action names exactly: `task.list`, `task.get`, `task.enqueue`, `task.create`.

### 테스트 작성

Write fake transport tests for:

- `listTasks` sends `{limit}` and parses `tasks`.
- `enqueueTask` returns the updated task from `payload.task`.
- error envelope `task.not_found` throws `ProtoSocketTaskException`.
- malformed success payload returns empty list or throws a service exception consistently; choose one and document in test name.

### 중간 검증

```bash
cd apps/client && flutter test test/integrations/proto_socket_task_service_test.dart
```

Expected: all task service tests pass.

## [API-3] Service Test Coverage

### 문제

Without service-level tests, later UI work could silently depend on mock data or incorrectly shaped envelopes.

### 해결 방법

Add `apps/client/test/integrations/proto_socket_task_service_test.dart` with a fake `ProtoSocketTransport` that records requests and returns queued `ProtoSocketEnvelope` responses. Do not use network sockets.

### 수정 파일 및 체크리스트

- [ ] Add fake transport fixture.
- [ ] Assert outgoing envelope `type`, `channel`, `action`, and `payload`.
- [ ] Assert error envelope mapping includes `retryable`.

### 테스트 작성

This item is the test implementation for API-1 and API-2.

### 중간 검증

```bash
cd apps/client && flutter test test/integrations/proto_socket_task_service_test.dart
```

Expected: all task service tests pass.

## 의존 관계 및 구현 순서

Directory dependency `02+01_task_service` means implementation must start only after `agent-task/m-proto-socket-infrastructure-communication-rail/01_client_transport/complete.log` exists.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/client/lib/src/features/workspaces/domain/workspace_task.dart` | API-1 |
| `apps/client/lib/src/integrations/proto_socket/proto_socket_task_service.dart` | API-2 |
| `apps/client/test/integrations/proto_socket_task_service_test.dart` | API-1, API-2, API-3 |

## 최종 검증

```bash
test -f agent-task/m-proto-socket-infrastructure-communication-rail/01_client_transport/complete.log
cd apps/client && flutter test test/integrations/proto_socket_task_service_test.dart
cd apps/client && flutter analyze --no-fatal-infos
```

Expected: predecessor complete.log exists; task service tests pass; analyzer has no fatal issues. Cached Flutter test output is acceptable only if no implementation files changed after the run.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
