<!-- task=m-client-integration-standardization/03+02_integration_boundaries plan=0 tag=CIS-INT -->

# Plan - CIS-INT

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

`CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현 후 검증 명령을 실행하고 실제 stdout/stderr를 붙여 넣은 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다.

## 배경

Client skeleton이 정리되면 integration boundary를 실제로 분리해야 한다. 현재 Mattermost push plugin singleton과 auth service가 app entrypoint에 직접 붙어 있고 proto-socket dependency는 아직 NomadCode client manifest에 없다. 이 작업은 proto-socket 설정/lifecycle 기준과 Mattermost host/plugin 책임 경계를 같은 `src/integrations` 방식으로 정리한다.

## 분석 결과

### 읽은 파일

- `agent-ops/roadmap/phase/project-workspace-management-ux/milestones/client-integration-standardization.md`
- `agent-ops/rules/project/domain/mobile/rules.md`
- `agent-ops/rules/project/domain/contracts/rules.md`
- `apps/mobile/pubspec.yaml`
- `apps/mobile/lib/main.dart`
- `apps/mobile/lib/services/mattermost_auth_service.dart`
- `apps/mobile/lib/services/workspace_launcher.dart`
- `apps/mobile/test/widget_test.dart`
- `apps/mobile/android/app/src/main/AndroidManifest.xml`
- `apps/mobile/android/app/build.gradle.kts`
- `apps/mobile/README.md`
- `apps/mobile/push-notification-todo.md`
- `packages/contracts/notes/flutter-core-api-candidates.md`
- `../proto-socket/README.md`
- `../proto-socket/dart/pubspec.yaml`
- `../proto-socket/dart/lib/proto_socket.dart`
- `../proto-socket/dart/lib/src/protobuf_client.dart`
- `../proto-socket/dart/lib/src/base_client.dart`
- `../proto-socket/dart/lib/src/ws_protobuf_client_web.dart`
- `../mattermost-push-plugin/pubspec.yaml`
- `../mattermost-push-plugin/README.md`

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

- Proto-socket dependency/config: no existing tests. Add config/lifecycle unit tests that do not open network sockets.
- Mattermost host integration: no existing tests cover plugin callback registration or auth handoff. Add tests around a fake host adapter, not real Firebase/FCM.
- Android manifest/plugin registration: no automated device/emulator test in this repo. Verify app test/analyze and document manual smoke in `04+03_clone_handoff_docs`.

### 심볼 참조

- `MattermostPushPlugin.instance`: direct references at `apps/mobile/lib/main.dart:11`, plugin handoff docs, and README. After this task, production singleton access should be isolated to one adapter file under `src/integrations/mattermost`.
- `MattermostAuthService`: direct construction at `apps/mobile/lib/main.dart:35`. Move construction behind a Mattermost host integration.
- `onNavigateToChannel` and `onNavigateToThread`: registration currently happens inside `MaterialApp.builder` at `apps/mobile/lib/main.dart:61-69`; move to integration setup so rebuilds do not reassign callbacks.
- `proto_socket`: no current `apps/mobile/pubspec.yaml` dependency. Available local Dart package is `../../../proto-socket/dart` after `apps/client` path.

### 분할 판단

split decision policy를 계획 파일 선택 전에 평가했다. 이 task는 `m-client-integration-standardization/03+02_integration_boundaries`이며 predecessor는 `02+01_client_bootstrap_skeleton`이다. Protocol/auth/plugin boundary carries higher risk than pure skeleton work, so it remains separate. `04+03_clone_handoff_docs` depends on this task for final integration facts.

### 범위 결정 근거

- Do not add new backend/core endpoints or schemas. Contracts changes are compatibility notes only.
- Do not change `../proto-socket` or `../mattermost-push-plugin`. Consume their existing local packages.
- Do not implement full iOS native push. iOS remains documented as a follow-up target on the same Dart API.
- Do not connect to a real proto-socket endpoint in tests. Use injected fake lifecycle connectors.

### 빌드 등급

- build lane: `cloud-G07`, review lane: `cloud-G07`. 이유: protocol dependency, auth/token handoff, platform plugin boundary, and weak automated coverage all apply.

## 의존 관계 및 구현 순서

`02+01_client_bootstrap_skeleton`가 PASS되어 `complete.log`를 만든 뒤 시작한다. 이 dependency는 directory name `03+02_integration_boundaries`의 source of truth와 일치한다.

## 구현 체크리스트

- [ ] [CIS-INT-1] proto-socket Dart dependency와 config/lifecycle facade를 추가한다.
- [ ] [CIS-INT-2] Mattermost push host integration을 plugin adapter 경계로 분리한다.
- [ ] [CIS-INT-3] integration tests와 contracts compatibility note를 갱신한다.
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [CIS-INT-1] proto-socket dependency와 lifecycle facade

#### 문제

Milestone은 proto-socket Dart dependency, endpoint configuration, connection lifecycle 기준을 요구한다. 현재 `apps/mobile/pubspec.yaml:39-49`에는 Firebase, Mattermost push, HTTP, url_launcher만 있다.

Before:

```yaml
# apps/mobile/pubspec.yaml:39-49
  firebase_core: ^3.13.0
  mattermost_push_plugin:
    path: ../../../mattermost-push-plugin
  http: ^1.3.0
  url_launcher: ^6.3.2
```

#### 해결 방법

`apps/client/pubspec.yaml`에 local path dependency를 추가한다. `src/integrations/proto_socket` 아래에 config와 lifecycle facade를 둔다. Lifecycle facade는 enum state, connect/disconnect, injected connector를 제공하고 default connector는 아직 app startup에서 자동 연결하지 않는다.

After:

```yaml
  proto_socket:
    path: ../../../proto-socket/dart
```

```dart
class ProtoSocketEndpointConfig {
  const ProtoSocketEndpointConfig({
    required this.host,
    required this.port,
    required this.secure,
    this.path = '/',
  });
}
```

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

- [ ] `apps/client/pubspec.yaml`
- [ ] `apps/client/pubspec.lock` after `flutter pub get`
- [ ] `apps/client/lib/src/integrations/proto_socket/proto_socket_endpoint_config.dart`
- [ ] `apps/client/lib/src/integrations/proto_socket/proto_socket_lifecycle.dart`
- [ ] `apps/client/lib/src/app/bootstrap.dart` only wires config creation, not automatic network connection unless explicitly disabled by default.

#### 테스트 작성

Write tests:

- `apps/client/test/integrations/proto_socket_endpoint_config_test.dart`: default path, secure scheme selection, environment parsing fallback.
- `apps/client/test/integrations/proto_socket_lifecycle_test.dart`: fake connector transitions disconnected -> connecting -> connected -> disconnected and failure -> failed.

#### 중간 검증

```bash
cd apps/client && flutter pub get && flutter test test/integrations/proto_socket_endpoint_config_test.dart test/integrations/proto_socket_lifecycle_test.dart
```

예상: tests pass.

### [CIS-INT-2] Mattermost host integration adapter

#### 문제

`apps/mobile/lib/main.dart:11-12` creates plugin and launcher globals, `apps/mobile/lib/main.dart:32-39` initializes plugin/auth, and `apps/mobile/lib/main.dart:61-69` registers navigation callbacks during `MaterialApp.builder`. This hides the host/plugin responsibility boundary and can repeat callback assignment on rebuild.

Before:

```dart
// apps/mobile/lib/main.dart:61-69
builder: (context, child) {
  _pushService.onNavigateToChannel = (serverUrl, channelId) {
    print('[Nav] Navigate to channel: $channelId on $serverUrl');
  };
  _pushService.onNavigateToThread = (serverUrl, rootId) {
    print('[Nav] Navigate to thread: $rootId on $serverUrl');
  };
  return child!;
},
```

#### 해결 방법

Define a small host-facing interface for push plugin operations and a production adapter that wraps `MattermostPushPlugin.instance`. Move plugin initialization, auth handoff, device token callback, and navigation callback registration into `MattermostPushHostIntegration`. App widget receives notification stream/callback dependencies through a lightweight app dependencies object or bootstrap composition.

After:

```dart
abstract interface class MattermostPushClient {
  Stream<Map<String, dynamic>> get onNotification;
  Future<void> initialize();
  Future<String?> getDeviceToken();
  set onDeviceTokenReady(Future<void> Function(String token)? callback);
  set onNavigateToChannel(void Function(String serverUrl, String channelId)? callback);
  set onNavigateToThread(void Function(String serverUrl, String rootId)? callback);
}
```

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

- [ ] `apps/client/lib/src/integrations/mattermost/mattermost_push_client.dart`
- [ ] `apps/client/lib/src/integrations/mattermost/mattermost_push_plugin_client.dart`
- [ ] `apps/client/lib/src/integrations/mattermost/mattermost_push_host_integration.dart`
- [ ] `apps/client/lib/src/integrations/mattermost/mattermost_auth_service.dart`
- [ ] `apps/client/lib/src/app/bootstrap.dart`
- [ ] `apps/client/lib/src/app/nomadcode_client_app.dart`
- [ ] `apps/client/lib/src/features/workspaces/presentation/workspace_home_page.dart` only if notification snackbar dependency changes.

#### 테스트 작성

Write `apps/client/test/integrations/mattermost_push_host_integration_test.dart` with a fake `MattermostPushClient`. Assert initialize is called once, auth service can be skipped when credentials are unavailable, navigation callbacks are assigned once, and notification stream remains consumable by app-level code.

#### 중간 검증

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

예상: test passes without Firebase/FCM channel calls.

### [CIS-INT-3] contracts note and import boundary verification

#### 문제

`packages/contracts/notes/flutter-core-api-candidates.md:3-4` still frames the client as `apps/mobile`, and no section records proto-socket endpoint/config fields or Mattermost host/plugin boundary as compatibility notes.

Before:

```markdown
# packages/contracts/notes/flutter-core-api-candidates.md:3-4
이 문서는 source schema가 아니라 Flutter-first 전환 중 공유 계약 후보를 모으는 compatibility note다.
실제 Go core 구현(`services/core`)과 향후 작성될 Flutter 클라이언트(`apps/mobile`) 간의 인터페이스 정합성을 보장하기 위한 임시 계약 후보이며...
```

#### 해결 방법

Update the note to `apps/client` and add a candidate section for client integration config:

- proto-socket endpoint fields: `host`, `port`, `secure`, `path`, `heartbeat_interval_seconds`, `heartbeat_wait_seconds`, `enabled`.
- Mattermost push host-owned fields: server URL, auth token handoff, signing key handoff, navigation callbacks, device token sync.
- State that actual source schema is deferred until a consuming core/client boundary stabilizes.

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

- [ ] `packages/contracts/notes/flutter-core-api-candidates.md`
- [ ] `apps/client/README.md` gets only brief pointer if needed; full handoff waits for `04+03_clone_handoff_docs`.

#### 테스트 작성

새 테스트는 작성하지 않는다. 문서/contract note 변경은 deterministic search로 검증한다.

#### 중간 검증

```bash
rg --sort path -n "proto-socket|mattermost_push_plugin|apps/client" packages/contracts/notes/flutter-core-api-candidates.md apps/client/README.md
```

예상: 새 integration boundary 문구가 출력된다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/client/pubspec.yaml` | CIS-INT-1 |
| `apps/client/pubspec.lock` | CIS-INT-1 |
| `apps/client/lib/src/integrations/proto_socket/proto_socket_endpoint_config.dart` | CIS-INT-1 |
| `apps/client/lib/src/integrations/proto_socket/proto_socket_lifecycle.dart` | CIS-INT-1 |
| `apps/client/lib/src/integrations/mattermost/mattermost_push_client.dart` | CIS-INT-2 |
| `apps/client/lib/src/integrations/mattermost/mattermost_push_plugin_client.dart` | CIS-INT-2 |
| `apps/client/lib/src/integrations/mattermost/mattermost_push_host_integration.dart` | CIS-INT-2 |
| `apps/client/lib/src/integrations/mattermost/mattermost_auth_service.dart` | CIS-INT-2 |
| `apps/client/lib/src/app/bootstrap.dart` | CIS-INT-1, CIS-INT-2 |
| `apps/client/lib/src/app/nomadcode_client_app.dart` | CIS-INT-2 |
| `apps/client/test/integrations/proto_socket_endpoint_config_test.dart` | CIS-INT-1 |
| `apps/client/test/integrations/proto_socket_lifecycle_test.dart` | CIS-INT-1 |
| `apps/client/test/integrations/mattermost_push_host_integration_test.dart` | CIS-INT-2 |
| `packages/contracts/notes/flutter-core-api-candidates.md` | CIS-INT-3 |

## 최종 검증

```bash
cd apps/client && flutter pub get
cd apps/client && dart format --set-exit-if-changed lib test
cd apps/client && flutter test
cd apps/client && flutter analyze --no-fatal-infos
rg --sort path -n "MattermostPushPlugin.instance" apps/client/lib apps/client/test
rg --sort path -n "proto-socket|proto_socket|mattermost_push_plugin|apps/client" packages/contracts/notes/flutter-core-api-candidates.md apps/client/README.md apps/client/pubspec.yaml
```

`MattermostPushPlugin.instance` search must show only the production adapter file. Go test cache 정책: 해당 없음. Flutter test cache 출력은 허용하지 않는다.

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