feat: client-validation task and update READMEs

- Add m-client-validation task directory
- Update apps/flutter-test/README.md
- Update packages/messaging_flutter/README.md
- Update packages/messaging_flutter/docs/android-test-environment.md
This commit is contained in:
toki 2026-05-28 20:08:15 +09:00
parent 0fbab633c5
commit 7d291aa90c
9 changed files with 1071 additions and 5 deletions

View file

@ -0,0 +1,171 @@
<!-- task=m-client-validation/01_docs_boundary plan=0 tag=DOCS -->
# Code Review Reference - DOCS
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a user-only decision, external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## 개요
date=2026-05-28
task=m-client-validation/01_docs_boundary, plan=0, tag=DOCS
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. `CODE_REVIEW-local-G04.md` -> `code_review_local_G04_0.log`, `PLAN-local-G04.md` -> `plan_local_G04_0.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-client-validation/01_docs_boundary/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이고 task group이 `m-client-validation`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [DOCS-1] flutter-test README 경계 보강 | [x] |
| [DOCS-2] plugin README SDK 경계와 smoke runbook 보강 | [x] |
| [DOCS-3] Android test environment guide 보강 | [x] |
## 구현 체크리스트
- [x] [DOCS-1] `apps/flutter-test/README.md`에 thin host 책임, plugin-owned SDK boundary, remote Android guide 연결을 명확히 한다.
- [x] [DOCS-2] `packages/messaging_flutter/README.md`에 nexo-owned embedded SDK 경계와 manual FCM/ACK smoke runbook의 사전조건/증거를 보강한다.
- [x] [DOCS-3] `packages/messaging_flutter/docs/android-test-environment.md`가 local Android SDK 없음, remote emulator, Gradle unit test 실패 조건을 분명히 설명하게 한다.
- [x] `rg --sort path -n "nexo product app|manual Firebase FCM|nexo-owned|android-test-environment|testDebugUnitTest|flutter test integration_test" apps/flutter-test/README.md packages/messaging_flutter/README.md packages/messaging_flutter/docs/android-test-environment.md`로 문서 기준선 문구를 확인한다.
- [x] `git diff --check -- apps/flutter-test/README.md packages/messaging_flutter/README.md packages/messaging_flutter/docs/android-test-environment.md`를 통과시킨다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [ ] active `CODE_REVIEW-*-G??.md``code_review_local_G04_0.log`로 아카이브한다.
- [ ] active `PLAN-*-G??.md``plan_local_G04_0.log`로 아카이브한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/m-client-validation/01_docs_boundary/``agent-task/archive/YYYY/MM/m-client-validation/01_docs_boundary/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-client-validation`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-client-validation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G04.md``CODE_REVIEW-local-G04.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
## 계획 대비 변경 사항
없음.
## 주요 설계 결정
- `apps/flutter-test` README에는 앱이 plugin API consumer/verification host임을 추가하고, native push/ACK/storage/rendering은 `packages/messaging_flutter` 소유로 남겼다.
- `packages/messaging_flutter` README에는 nexo-owned embedded SDK 경계를 별도 섹션으로 두고, Mattermost mobile app source를 팔로잉하지 않는다는 기준을 명시했다.
- manual FCM/ACK smoke는 자동 테스트가 아니라 외부 Firebase/server 인프라가 필요한 checklist로 유지하고, prerequisites/evidence만 보강했다.
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 외부 환경 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. code-review가 이 내용을 검증해 `USER_REVIEW.md`를 작성한다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- 문서가 `apps/flutter-test`를 제품 앱으로 확장하지 않는지 확인한다.
- SDK boundary가 Mattermost mobile app code following과 혼동되지 않는지 확인한다.
- manual FCM/ACK smoke가 자동 검증과 명확히 분리되었는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
### DOCS-1 중간 검증
```sh
$ rg --sort path -n "plugin API consumer|source of truth|android-test-environment" apps/flutter-test/README.md
21:The app is a plugin API consumer and verification host. Native push handling,
81:[`../../packages/messaging_flutter/docs/android-test-environment.md`](../../packages/messaging_flutter/docs/android-test-environment.md).
```
### DOCS-2 중간 검증
```sh
$ rg --sort path -n "nexo-owned|Mattermost mobile|Prerequisites|Evidence" packages/messaging_flutter/README.md
13:This package is the nexo-owned embedded messaging SDK. Its Android
16:the Mattermost mobile app source tree; that app remains under `apps/mattermost/`
252:Prerequisites:
261:Evidence to capture:
```
### DOCS-3 중간 검증
```sh
$ rg --sort path -n "SDK location not found|ANDROID_HOME|sdk.dir|remote" packages/messaging_flutter/docs/android-test-environment.md
14:Use these environment variables for every remote Android test session:
18:export ANDROID_HOME="$HOME/Library/Android/sdk"
19:export ANDROID_SDK_ROOT="$ANDROID_HOME"
24:Before using the remote host, check whether the local machine already has the
34:`./gradlew testDebugUnitTest` requires either `ANDROID_HOME`/`ANDROID_SDK_ROOT`
35:or a valid `sdk.dir` entry in `apps/flutter-test/android/local.properties`. If
36:the command fails with `SDK location not found`, treat it as a local environment
37:blocker and use the remote flow below instead of changing repository files.
42:remote directory. This is useful when local changes are not committed yet.
69:export ANDROID_HOME="$HOME/Library/Android/sdk"
70:export ANDROID_SDK_ROOT="$ANDROID_HOME"
```
### 최종 검증
```sh
$ rg --sort path -n "plugin API consumer|source of truth|nexo-owned|Mattermost mobile|Prerequisites|Evidence|SDK location not found|ANDROID_HOME|sdk.dir" apps/flutter-test/README.md packages/messaging_flutter/README.md packages/messaging_flutter/docs/android-test-environment.md
apps/flutter-test/README.md:21:The app is a plugin API consumer and verification host. Native push handling,
apps/flutter-test/README.md:74:Android Gradle tests require a configured Android SDK through `ANDROID_HOME`,
packages/messaging_flutter/README.md:13:This package is the nexo-owned embedded messaging SDK. Its Android
packages/messaging_flutter/README.md:16:the Mattermost mobile app source tree; that app remains under `apps/mattermost/`
packages/messaging_flutter/README.md:194:Android Gradle tests require a configured Android SDK through `ANDROID_HOME` or
packages/messaging_flutter/README.md:232:Use the repository tests as the source of truth for plugin behavior:
packages/messaging_flutter/README.md:252:Prerequisites:
packages/messaging_flutter/README.md:261:Evidence to capture:
packages/messaging_flutter/docs/android-test-environment.md:18:export ANDROID_HOME="$HOME/Library/Android/sdk"
packages/messaging_flutter/docs/android-test-environment.md:19:export ANDROID_SDK_ROOT="$ANDROID_HOME"
packages/messaging_flutter/docs/android-test-environment.md:34:`./gradlew testDebugUnitTest` requires either `ANDROID_HOME`/`ANDROID_SDK_ROOT`
packages/messaging_flutter/docs/android-test-environment.md:35:or a valid `sdk.dir` entry in `apps/flutter-test/android/local.properties`. If
packages/messaging_flutter/docs/android-test-environment.md:36:the command fails with `SDK location not found`, treat it as a local environment
$ git diff --check -- apps/flutter-test/README.md packages/messaging_flutter/README.md packages/messaging_flutter/docs/android-test-environment.md
(no output)
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
Sections and their ownership:
| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
| 구현 항목별 완료 여부 | Fixed at stub creation | Implementing agent checks `[ ]` -> `[x]` only |
| 구현 체크리스트 | Fixed at stub creation from plan | Implementing agent checks `[ ]` -> `[x]` only |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless user input is required |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
| 검증 결과 | Fixed headings and commands | Implementing agent fills command output only |

View file

@ -0,0 +1,188 @@
<!-- task=m-client-validation/01_docs_boundary plan=0 tag=DOCS -->
# Plan - DOCS
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증을 실행하고 실제 변경 내용, 명령 출력, 계획 대비 변경 사항을 채운 뒤 active 파일을 그대로 둔 채 리뷰 준비 상태로 보고한다. 최종 판정, 로그 rename, `complete.log`, archive 이동은 code-review 스킬 전용이다. 사용자만 결정할 수 있는 사항, 외부 환경 준비, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정 필요 사항과 근거를 채우고 멈춘다.
## 배경
현재 Milestone은 `apps/flutter-test`를 제품 앱이 아닌 얇은 플러그인 검증 호스트로 고정해야 한다. README들은 이미 방향을 담고 있지만, SDK ownership boundary와 수동 FCM/ACK smoke runbook이 한눈에 이어지지 않는다. 먼저 문서 기준선을 정리해야 후속 테스트 작업이 범위를 넓히지 않는다.
## 사용자 리뷰 요청 흐름
구현 중 blocker는 active `CODE_REVIEW-*-G??.md``사용자 리뷰 요청` 섹션에 기록한다. 섹션 형식은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`와 같으며, code-review가 검증 후 실제 `USER_REVIEW.md` 작성 여부를 결정한다.
## 분석 결과
### 읽은 파일
- `agent-roadmap/current.md`
- `agent-roadmap/phase/product-foundation/PHASE.md`
- `agent-roadmap/phase/product-foundation/milestones/client-validation.md`
- `agent-ops/rules/project/domain/client-app/rules.md`
- `agent-ops/rules/project/domain/messaging-flutter/rules.md`
- `agent-ops/rules/project/domain/workspace-ops/rules.md`
- `apps/flutter-test/README.md`
- `packages/messaging_flutter/README.md`
- `packages/messaging_flutter/docs/android-test-environment.md`
- `apps/flutter-test/lib/main.dart`
- `apps/flutter-test/test/widget_test.dart`
- `apps/flutter-test/integration_test/plugin_integration_test.dart`
- `packages/messaging_flutter/lib/nexo_messaging.dart`
- `packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart`
- `packages/messaging_flutter/lib/src/notification_opened_event.dart`
- `packages/messaging_flutter/lib/src/push_notification_type.dart`
- `packages/messaging_flutter/test/nexo_messaging_test.dart`
- `apps/flutter-test/pubspec.yaml`
- `packages/messaging_flutter/pubspec.yaml`
### 테스트 커버리지 공백
- 문서 기준선 변경: 자동 테스트는 직접 커버하지 않는다. `rg --sort path` 검색과 `git diff --check`로 링크/용어 회귀를 확인한다.
- SDK ownership boundary 설명: 기존 Dart/widget/integration 테스트가 동작 자체를 커버하지만 README 문구 자체는 테스트하지 않는다.
- FCM/ACK manual smoke runbook: 외부 Firebase/server 인프라가 필요해 자동화하지 않는다.
### 심볼 참조
- 변경/삭제할 심볼 없음.
- 확인한 관련 참조: `rg --sort path -n "android-test-environment|google-services|ACK|Manual Smoke|testDebugUnitTest|flutter test integration_test" apps/flutter-test packages/messaging_flutter`.
### 분할 판단
- split decision policy를 plan 파일 선택 전에 평가했다.
- 공유 task group: `agent-task/m-client-validation`.
- 이 subtask: `01_docs_boundary`, 선행 없음.
- 후속: `02+01_flutter_contract_tests`는 문서 기준선 후 테스트 계약을 보강한다. `03+01,02_android_native_validation`은 문서와 Flutter 계약 테스트 후 Android/원격 검증을 정리한다.
### 범위 결정 근거
- `apps/mattermost/**`, `services/**`는 이 Milestone의 client validation 범위가 아니므로 제외한다.
- `apps/flutter-test`를 제품 UI로 확장하지 않는다. README 문구와 문서 링크만 정리한다.
- `docs/` root에는 환경별 host/token/runbook을 두지 않는 private rule 때문에 새 root docs를 만들지 않는다.
### 빌드 등급
- build=`local-G04`, review=`local-G04`. 문서 중심이고 자동 검증은 검색/공백 검사지만, 두 도메인 README를 연결하므로 G04로 둔다.
## 구현 체크리스트
- [ ] [DOCS-1] `apps/flutter-test/README.md`에 thin host 책임, plugin-owned SDK boundary, remote Android guide 연결을 명확히 한다.
- [ ] [DOCS-2] `packages/messaging_flutter/README.md`에 nexo-owned embedded SDK 경계와 manual FCM/ACK smoke runbook의 사전조건/증거를 보강한다.
- [ ] [DOCS-3] `packages/messaging_flutter/docs/android-test-environment.md`가 local Android SDK 없음, remote emulator, Gradle unit test 실패 조건을 분명히 설명하게 한다.
- [ ] `rg --sort path -n "nexo product app|manual Firebase FCM|nexo-owned|android-test-environment|testDebugUnitTest|flutter test integration_test" apps/flutter-test/README.md packages/messaging_flutter/README.md packages/messaging_flutter/docs/android-test-environment.md`로 문서 기준선 문구를 확인한다.
- [ ] `git diff --check -- apps/flutter-test/README.md packages/messaging_flutter/README.md packages/messaging_flutter/docs/android-test-environment.md`를 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [DOCS-1] flutter-test README 경계 보강
#### 문제
[apps/flutter-test/README.md](/config/workspace/nexo/apps/flutter-test/README.md:7)는 test harness 경계를 설명하지만 SDK ownership boundary와 manual smoke owner가 분리되어 보이지 않는다.
Before:
```markdown
7 This app is the workspace test harness for `packages/messaging_flutter`. Keep it
8 small and focused on proving the messaging/notification package contract:
```
#### 해결 방법
`Purpose` 뒤에 이 앱은 plugin API 소비자이며 native push/ACK/storage source of truth는 `packages/messaging_flutter`라는 문장을 추가한다. `Verification`에는 local에서 device가 없으면 integration test가 실패할 수 있고 remote guide를 쓰라는 연결을 더 직접적으로 둔다.
#### 수정 파일 및 체크리스트
- [ ] `apps/flutter-test/README.md`
#### 테스트 작성
문서 변경이라 테스트 파일은 추가하지 않는다.
#### 중간 검증
```sh
rg --sort path -n "plugin API consumer|source of truth|android-test-environment" apps/flutter-test/README.md
```
### [DOCS-2] plugin README SDK 경계와 smoke runbook 보강
#### 문제
[packages/messaging_flutter/README.md](/config/workspace/nexo/packages/messaging_flutter/README.md:6)는 plugin이 push domain을 소유한다고 설명하지만, Mattermost mobile code를 팔로잉하지 않는 nexo-owned embedded SDK 경계는 명시적이지 않다. [packages/messaging_flutter/README.md](/config/workspace/nexo/packages/messaging_flutter/README.md:238)의 manual checklist도 사전조건과 증거 형식이 부족하다.
Before:
```markdown
6 The plugin owns the push-notification domain that should not be duplicated in
7 each consuming app: native FCM handling, notification display, notification tap
8 events, inline replies, dismiss handling, ACK delivery, device-token storage,
9 auth-token storage, and server signing-key storage.
```
#### 해결 방법
`Platform Status` 전후에 SDK ownership boundary를 추가한다. `Manual Smoke Checklist`를 유지하되 `Prerequisites``Evidence to capture`를 더해 Firebase config, server URL/auth/signing key, logcat/notification/ACK evidence를 기록하게 한다.
#### 수정 파일 및 체크리스트
- [ ] `packages/messaging_flutter/README.md`
#### 테스트 작성
문서 변경이라 테스트 파일은 추가하지 않는다.
#### 중간 검증
```sh
rg --sort path -n "nexo-owned|Mattermost mobile|Prerequisites|Evidence" packages/messaging_flutter/README.md
```
### [DOCS-3] Android test environment guide 보강
#### 문제
[packages/messaging_flutter/docs/android-test-environment.md](/config/workspace/nexo/packages/messaging_flutter/docs/android-test-environment.md:73)는 remote check 명령을 제공하지만, local failure mode와 Gradle SDK precondition이 README와 같은 언어로 연결되지 않는다. 현재 local `./gradlew testDebugUnitTest``SDK location not found`로 실패했다.
Before:
```markdown
73 ## Run The Checks
75 Run the deterministic Flutter checks:
```
#### 해결 방법
`Run The Checks` 앞에 local preflight와 expected fallback을 추가한다. `flutter doctor -v`, `ANDROID_HOME`, `local.properties sdk.dir` 확인을 넣고, local Android SDK가 없으면 remote run이 source of truth임을 적는다.
#### 수정 파일 및 체크리스트
- [ ] `packages/messaging_flutter/docs/android-test-environment.md`
#### 테스트 작성
문서 변경이라 테스트 파일은 추가하지 않는다.
#### 중간 검증
```sh
rg --sort path -n "SDK location not found|ANDROID_HOME|sdk.dir|remote" packages/messaging_flutter/docs/android-test-environment.md
```
## 수정 파일 요약
| 파일 | 항목 |
| --- | --- |
| `apps/flutter-test/README.md` | DOCS-1 |
| `packages/messaging_flutter/README.md` | DOCS-2 |
| `packages/messaging_flutter/docs/android-test-environment.md` | DOCS-3 |
## 최종 검증
```sh
rg --sort path -n "plugin API consumer|source of truth|nexo-owned|Mattermost mobile|Prerequisites|Evidence|SDK location not found|ANDROID_HOME|sdk.dir" apps/flutter-test/README.md packages/messaging_flutter/README.md packages/messaging_flutter/docs/android-test-environment.md
git diff --check -- apps/flutter-test/README.md packages/messaging_flutter/README.md packages/messaging_flutter/docs/android-test-environment.md
```
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,133 @@
<!-- task=m-client-validation/02+01_flutter_contract_tests plan=0 tag=TEST -->
# Code Review Reference - TEST
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a user-only decision, external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## 개요
date=2026-05-28
task=m-client-validation/02+01_flutter_contract_tests, plan=0, tag=TEST
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G05.md` -> `code_review_cloud_G05_0.log`, `PLAN-cloud-G05.md` -> `plan_cloud_G05_0.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-client-validation/02+01_flutter_contract_tests/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이고 task group이 `m-client-validation`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [TEST-1] integration test exact contract 보강 | [ ] |
| [TEST-2] local Dart/widget 계약 보강 | [ ] |
## 구현 체크리스트
- [ ] [TEST-1] `apps/flutter-test/integration_test/plugin_integration_test.dart`가 notification/action channel constants와 exact opened-routing UI state를 검증하게 보강한다.
- [ ] [TEST-2] `apps/flutter-test/test/widget_test.dart` 또는 `packages/messaging_flutter/test/nexo_messaging_test.dart`에 integration에서 device 때문에 못 보는 계약을 local test로 보강한다.
- [ ] `flutter test``packages/messaging_flutter`에서 실행해 package Dart contract가 통과함을 기록한다.
- [ ] `flutter test``apps/flutter-test`에서 실행해 thin host widget test가 통과함을 기록한다.
- [ ] `flutter test integration_test``apps/flutter-test`에서 실행한다. device 없음이면 실제 실패 출력을 기록하고, remote/device 검증은 후속 `03+01,02_android_native_validation`에 남긴다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [ ] active `CODE_REVIEW-*-G??.md``code_review_cloud_G05_0.log`로 아카이브한다.
- [ ] active `PLAN-*-G??.md``plan_cloud_G05_0.log`로 아카이브한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/m-client-validation/02+01_flutter_contract_tests/``agent-task/archive/YYYY/MM/m-client-validation/02+01_flutter_contract_tests/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-client-validation`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-client-validation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G05.md``CODE_REVIEW-cloud-G05.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 외부 환경 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. code-review가 이 내용을 검증해 `USER_REVIEW.md`를 작성한다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `01_docs_boundary` complete dependency가 충족된 뒤 구현되었는지 확인한다.
- integration test가 exact opened state를 검증하되 product UI를 늘리지 않았는지 확인한다.
- local test 보강이 integration gap을 정직하게 줄이는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
### TEST-1 중간 검증
```sh
$ flutter test integration_test
```
### TEST-2 중간 검증
```sh
$ cd packages/messaging_flutter && flutter test
$ cd apps/flutter-test && flutter test
```
### 최종 검증
```sh
$ cd packages/messaging_flutter && flutter test
$ cd apps/flutter-test && flutter test
$ cd apps/flutter-test && flutter test integration_test
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
Sections and their ownership:
| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
| 구현 항목별 완료 여부 | Fixed at stub creation | Implementing agent checks `[ ]` -> `[x]` only |
| 구현 체크리스트 | Fixed at stub creation from plan | Implementing agent checks `[ ]` -> `[x]` only |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless user input is required |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
| 검증 결과 | Fixed headings and commands | Implementing agent fills command output only |

View file

@ -0,0 +1,166 @@
<!-- task=m-client-validation/02+01_flutter_contract_tests plan=0 tag=TEST -->
# Plan - TEST
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증을 실행하고 실제 변경 내용, 명령 출력, 계획 대비 변경 사항을 채운 뒤 active 파일을 그대로 둔 채 리뷰 준비 상태로 보고한다. 최종 판정, 로그 rename, `complete.log`, archive 이동은 code-review 스킬 전용이다. 사용자만 결정할 수 있는 사항, 외부 환경 준비, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정 필요 사항과 근거를 채우고 멈춘다.
## 배경
`apps/flutter-test`의 Dart widget test와 integration test는 plugin registration, method channel, event channel, opened-routing 경로를 보호해야 한다. 현재 unit/widget 테스트는 통과하지만 integration test는 local Android device/emulator가 없어 실행되지 않는다. 이 subtask는 device-dependent integration test를 더 결정적으로 만들고 local에서 가능한 테스트 계약을 보강한다.
## 사용자 리뷰 요청 흐름
구현 중 blocker는 active `CODE_REVIEW-*-G??.md``사용자 리뷰 요청` 섹션에 기록한다. 섹션 형식은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`와 같으며, code-review가 검증 후 실제 `USER_REVIEW.md` 작성 여부를 결정한다.
## 분석 결과
### 읽은 파일
- `apps/flutter-test/lib/main.dart`
- `apps/flutter-test/test/widget_test.dart`
- `apps/flutter-test/integration_test/plugin_integration_test.dart`
- `apps/flutter-test/pubspec.yaml`
- `packages/messaging_flutter/lib/nexo_messaging.dart`
- `packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart`
- `packages/messaging_flutter/lib/src/notification_opened_event.dart`
- `packages/messaging_flutter/lib/src/push_notification_type.dart`
- `packages/messaging_flutter/test/nexo_messaging_test.dart`
- `packages/messaging_flutter/pubspec.yaml`
- `packages/messaging_flutter/README.md`
- `apps/flutter-test/README.md`
### 테스트 커버리지 공백
- plugin registration constant: integration test checks `notificationChannelName` only; `actionChannelName` is only checked in package unit test.
- event-channel opened-routing: integration test checks text contains IDs but not exact `Last opened` state.
- method-channel debug event: package unit test checks Dart method invocation; integration test checks native route via `debugSendNativeEventForTesting`.
- local environment gap: `flutter test integration_test` currently fails with no supported Android device. This is environment-dependent and must be recorded, not hidden.
### 심볼 참조
- 변경/삭제할 심볼 없음.
- 관련 참조는 `rg --sort path -n "debugSendNativeEvent|listenNativeChannelForTesting|notificationChannelName|actionChannelName|handleNativeEvent" apps/flutter-test packages/messaging_flutter`로 확인했다.
### 분할 판단
- split decision policy를 plan 파일 선택 전에 평가했다.
- shared task group: `agent-task/m-client-validation`.
- predecessor: `01_docs_boundary` must produce `complete.log` first because it fixes the test-host scope and verification wording.
- this subtask: `02+01_flutter_contract_tests`.
- dependent sibling: `03+01,02_android_native_validation` waits for this because it will rely on the finalized Flutter contract test shape.
### 범위 결정 근거
- Native Kotlin/JVM test changes are excluded here and handled by `03+01,02_android_native_validation`.
- No public Dart API rename or package identifier change is allowed in this Milestone subtask.
- No product UI is added to `apps/flutter-test`.
### 빌드 등급
- build=`cloud-G05`, review=`cloud-G05`. The code change is small, but integration verification is device-dependent and local tests do not fully cover the behavior.
## 의존 관계 및 구현 순서
- Runtime dependency from directory name: wait for `agent-task/m-client-validation/01_docs_boundary/complete.log`.
- Do not start this subtask until the predecessor has PASS completion.
## 구현 체크리스트
- [ ] [TEST-1] `apps/flutter-test/integration_test/plugin_integration_test.dart`가 notification/action channel constants와 exact opened-routing UI state를 검증하게 보강한다.
- [ ] [TEST-2] `apps/flutter-test/test/widget_test.dart` 또는 `packages/messaging_flutter/test/nexo_messaging_test.dart`에 integration에서 device 때문에 못 보는 계약을 local test로 보강한다.
- [ ] `flutter test``packages/messaging_flutter`에서 실행해 package Dart contract가 통과함을 기록한다.
- [ ] `flutter test``apps/flutter-test`에서 실행해 thin host widget test가 통과함을 기록한다.
- [ ] `flutter test integration_test``apps/flutter-test`에서 실행한다. device 없음이면 실제 실패 출력을 기록하고, remote/device 검증은 후속 `03+01,02_android_native_validation`에 남긴다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [TEST-1] integration test exact contract 보강
#### 문제
[apps/flutter-test/integration_test/plugin_integration_test.dart](/config/workspace/nexo/apps/flutter-test/integration_test/plugin_integration_test.dart:10)는 singleton과 notification channel만 확인한다. [apps/flutter-test/integration_test/plugin_integration_test.dart](/config/workspace/nexo/apps/flutter-test/integration_test/plugin_integration_test.dart:32)는 `find.textContaining` 위주라 opened state regression을 덜 정확히 잡는다.
Before:
```dart
10 testWidgets('singleton instance is accessible', (WidgetTester tester) async {
11 expect(NexoMessagingPlugin.instance, isNotNull);
12 expect(
13 NexoMessagingPlugin.notificationChannelName,
14 'com.tokilabs.nexo.messaging/notifications',
15 );
16 });
```
#### 해결 방법
`actionChannelName` assertion을 추가한다. channel/thread integration tests에는 exact `Last opened: ...` assertion을 추가하되 existing navigation assertions는 유지한다.
#### 수정 파일 및 체크리스트
- [ ] `apps/flutter-test/integration_test/plugin_integration_test.dart`
#### 테스트 작성
기존 integration test를 보강한다. 새 파일은 만들지 않는다.
#### 중간 검증
```sh
flutter test integration_test
```
예상: local Android device/emulator가 없으면 현재처럼 "No supported devices connected"로 실패할 수 있다. 그 경우 출력 전체를 review stub에 기록한다.
### [TEST-2] local Dart/widget 계약 보강
#### 문제
[packages/messaging_flutter/test/nexo_messaging_test.dart](/config/workspace/nexo/packages/messaging_flutter/test/nexo_messaging_test.dart:192)는 channel constants를 커버하지만 `apps/flutter-test/test/widget_test.dart`는 debug/native event path 대신 direct `handleNativeEvent`만 본다. device 없는 환경에서 integration gap을 보완할 local assertion이 더 필요하다.
Before:
```dart
192 group('Channel constants', () {
193 test('notification and action channel names are preserved', () {
```
#### 해결 방법
가능하면 existing local tests에 small assertion을 추가한다. 예: widget test initial state가 product-like controls 없이 status text만 렌더링하는 thin host임을 확인하거나 package unit test에 `message` without `userInteraction` does not navigate assertion을 추가한다. 새 public API는 만들지 않는다.
#### 수정 파일 및 체크리스트
- [ ] `apps/flutter-test/test/widget_test.dart` 또는 `packages/messaging_flutter/test/nexo_messaging_test.dart`
#### 테스트 작성
기존 test file에 regression assertion을 추가한다.
#### 중간 검증
```sh
cd packages/messaging_flutter && flutter test
cd apps/flutter-test && flutter test
```
## 수정 파일 요약
| 파일 | 항목 |
| --- | --- |
| `apps/flutter-test/integration_test/plugin_integration_test.dart` | TEST-1 |
| `apps/flutter-test/test/widget_test.dart` | TEST-2 |
| `packages/messaging_flutter/test/nexo_messaging_test.dart` | TEST-2 |
## 최종 검증
```sh
cd packages/messaging_flutter && flutter test
cd apps/flutter-test && flutter test
cd apps/flutter-test && flutter test integration_test
```
`flutter test integration_test`가 local device 부재로 실패하면 그 실제 출력을 기록하고, remote/device 재검증은 `03+01,02_android_native_validation`에서 수행한다.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,148 @@
<!-- task=m-client-validation/03+01,02_android_native_validation plan=0 tag=ANDROID -->
# Code Review Reference - ANDROID
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a user-only decision, external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## 개요
date=2026-05-28
task=m-client-validation/03+01,02_android_native_validation, plan=0, tag=ANDROID
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_0.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_0.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-client-validation/03+01,02_android_native_validation/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이고 task group이 `m-client-validation`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [ANDROID-1] Native unit test 계약 보강 | [ ] |
| [ANDROID-2] Local Android preflight 기록 | [ ] |
| [ANDROID-3] Remote/device verification handoff | [ ] |
## 구현 체크리스트
- [ ] [ANDROID-1] `packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt`를 plugin method-call/intent payload 계약 중심으로 보강한다.
- [ ] [ANDROID-2] local Android preflight를 실행하고 `./gradlew testDebugUnitTest`의 실제 통과 또는 SDK blocker 출력을 기록한다.
- [ ] [ANDROID-3] remote Android guide를 사용 가능한 경우 integration/native checks를 실행하고, 불가능하면 review stub의 사용자 리뷰 요청에 필요한 외부 준비 사항을 정확히 남긴다.
- [ ] `flutter doctor -v``command -v adb`/`command -v emulator`/`command -v sdkmanager` 결과를 기록한다.
- [ ] `./gradlew testDebugUnitTest``apps/flutter-test/android`에서 실행하고 실제 stdout/stderr를 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [ ] active `CODE_REVIEW-*-G??.md``code_review_cloud_G07_0.log`로 아카이브한다.
- [ ] active `PLAN-*-G??.md``plan_cloud_G07_0.log`로 아카이브한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/m-client-validation/03+01,02_android_native_validation/``agent-task/archive/YYYY/MM/m-client-validation/03+01,02_android_native_validation/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-client-validation`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-client-validation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md``CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 외부 환경 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. code-review가 이 내용을 검증해 `USER_REVIEW.md`를 작성한다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- predecessor `01_docs_boundary``02+01_flutter_contract_tests`가 complete인지 확인한다.
- native tests가 template `getPlatformVersion` 수준을 넘어 plugin contract를 검증하는지 확인한다.
- Android SDK/device blocker를 PASS처럼 처리하지 않았는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
### ANDROID-1 중간 검증
```sh
$ cd apps/flutter-test/android && ./gradlew testDebugUnitTest
```
### ANDROID-2 중간 검증
```sh
$ command -v adb
$ command -v emulator
$ command -v sdkmanager
$ flutter doctor -v
$ cd apps/flutter-test/android && ./gradlew testDebugUnitTest
```
### ANDROID-3 중간 검증
```sh
$ cd apps/flutter-test && flutter test integration_test -d emulator-5554
$ cd apps/flutter-test/android && ./gradlew testDebugUnitTest
```
### 최종 검증
```sh
$ command -v adb
$ command -v emulator
$ command -v sdkmanager
$ flutter doctor -v
$ cd apps/flutter-test/android && ./gradlew testDebugUnitTest
$ cd apps/flutter-test && flutter test integration_test -d emulator-5554
$ git diff --check -- packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt packages/messaging_flutter/android/build.gradle.kts packages/messaging_flutter/docs/android-test-environment.md
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
Sections and their ownership:
| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
| 구현 항목별 완료 여부 | Fixed at stub creation | Implementing agent checks `[ ]` -> `[x]` only |
| 구현 체크리스트 | Fixed at stub creation from plan | Implementing agent checks `[ ]` -> `[x]` only |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless user input is required |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
| 검증 결과 | Fixed headings and commands | Implementing agent fills command output only |

View file

@ -0,0 +1,209 @@
<!-- task=m-client-validation/03+01,02_android_native_validation plan=0 tag=ANDROID -->
# Plan - ANDROID
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증을 실행하고 실제 변경 내용, 명령 출력, 계획 대비 변경 사항을 채운 뒤 active 파일을 그대로 둔 채 리뷰 준비 상태로 보고한다. 최종 판정, 로그 rename, `complete.log`, archive 이동은 code-review 스킬 전용이다. 사용자만 결정할 수 있는 사항, 외부 환경 준비, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정 필요 사항과 근거를 채우고 멈춘다.
## 배경
Milestone은 Android native unit test와 remote Android verification path를 기준선으로 요구한다. 현재 local 환경은 Android SDK가 없어 `./gradlew testDebugUnitTest``SDK location not found`로 실패하고, integration test도 Android device/emulator가 없어 실패한다. 이 subtask는 native test 내용을 템플릿성 확인에서 실제 plugin contract로 키우고, local/remote verification을 정직하게 기록한다.
## 사용자 리뷰 요청 흐름
구현 중 blocker는 active `CODE_REVIEW-*-G??.md``사용자 리뷰 요청` 섹션에 기록한다. 섹션 형식은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`와 같으며, code-review가 검증 후 실제 `USER_REVIEW.md` 작성 여부를 결정한다.
## 분석 결과
### 읽은 파일
- `apps/flutter-test/android/local.properties`
- `apps/flutter-test/android/settings.gradle.kts`
- `apps/flutter-test/android/build.gradle.kts`
- `apps/flutter-test/android/app/build.gradle.kts`
- `apps/flutter-test/android/app/src/main/AndroidManifest.xml`
- `packages/messaging_flutter/android/build.gradle.kts`
- `packages/messaging_flutter/android/src/main/AndroidManifest.xml`
- `packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPlugin.kt`
- `packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/PushNotificationEvents.kt`
- `packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/LaunchIntentHelper.kt`
- `packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoFirebaseMessagingService.kt`
- `packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt`
- `packages/messaging_flutter/docs/android-test-environment.md`
- `apps/flutter-test/README.md`
- `packages/messaging_flutter/README.md`
### 테스트 커버리지 공백
- `NexoMessagingPlugin.onMethodCall`: current native test only checks `getPlatformVersion`; invalid argument paths and debug event method are not covered.
- `intentPayload`: no native unit test currently verifies extras -> opened/userInteraction payload mapping.
- Local Android verification: blocked because local `apps/flutter-test/android/local.properties` has `flutter.sdk=/sdk/flutter` but no `sdk.dir`, and `ANDROID_HOME` is unavailable.
- Remote Android verification: documented but not executed in this local session.
### 심볼 참조
- 변경/삭제할 심볼 없음.
- Relevant references checked with `rg --sort path -n "debugSendNativeEvent|intentPayload|testDebugUnitTest|ANDROID_HOME|sdk.dir" apps/flutter-test packages/messaging_flutter`.
### 분할 판단
- split decision policy를 plan 파일 선택 전에 평가했다.
- shared task group: `agent-task/m-client-validation`.
- predecessor dependencies from directory name: `01_docs_boundary` and `02+01_flutter_contract_tests`.
- this subtask: `03+01,02_android_native_validation`, waits for both because it relies on finalized doc language and Flutter contract tests.
### 범위 결정 근거
- No remote credentials or host-specific tokens are added to tracked docs.
- No Android SDK is installed into the repository.
- No public package rename, manifest identifier rename, or generated Flutter platform file regeneration is allowed.
- FCM real delivery remains manual smoke; this subtask may document/run it only when external infrastructure is already prepared.
### 빌드 등급
- build=`cloud-G07`, review=`cloud-G07`. Success depends on terminal/Gradle workflow, environment diagnosis, Android SDK/device availability, and possibly remote execution.
## 의존 관계 및 구현 순서
- Wait for `agent-task/m-client-validation/01_docs_boundary/complete.log`.
- Wait for `agent-task/m-client-validation/02+01_flutter_contract_tests/complete.log`.
- Do not add dependencies beyond the `03+01,02_...` directory name.
## 구현 체크리스트
- [ ] [ANDROID-1] `packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt`를 plugin method-call/intent payload 계약 중심으로 보강한다.
- [ ] [ANDROID-2] local Android preflight를 실행하고 `./gradlew testDebugUnitTest`의 실제 통과 또는 SDK blocker 출력을 기록한다.
- [ ] [ANDROID-3] remote Android guide를 사용 가능한 경우 integration/native checks를 실행하고, 불가능하면 review stub의 사용자 리뷰 요청에 필요한 외부 준비 사항을 정확히 남긴다.
- [ ] `flutter doctor -v``command -v adb`/`command -v emulator`/`command -v sdkmanager` 결과를 기록한다.
- [ ] `./gradlew testDebugUnitTest``apps/flutter-test/android`에서 실행하고 실제 stdout/stderr를 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [ANDROID-1] Native unit test 계약 보강
#### 문제
[packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt](/config/workspace/nexo/packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt:8)는 template comment와 `getPlatformVersion` 단일 테스트만 갖고 있다. [packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPlugin.kt](/config/workspace/nexo/packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPlugin.kt:139)의 `debugSendNativeEvent` invalid argument path와 [packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPlugin.kt](/config/workspace/nexo/packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPlugin.kt:122)의 `intentPayload` mapping은 native test로 고정되어 있지 않다.
Before:
```kotlin
16 internal class NexoMessagingPluginTest {
17 @Test
18 fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
```
#### 해결 방법
기존 template comment를 제거하고, 최소한 다음 native tests를 추가한다.
- `onMethodCall_debugSendNativeEvent_withInvalidPayload_returnsInvalidArg`
- `onMethodCall_saveDeviceToken_withoutToken_returnsInvalidArg`
- 가능하면 Android SDK/Robolectric 없이 가능한 범위에서 `intentPayload` mapping test. 필요하면 implementation blocker로 남기고 문서화한다.
새 dependency는 추가하지 않는 것을 기본으로 한다. Robolectric이 꼭 필요하면 `packages/messaging_flutter/android/build.gradle.kts``testImplementation` 추가 여부와 이유를 review stub에 기록한다.
#### 수정 파일 및 체크리스트
- [ ] `packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt`
- [ ] 필요 시 `packages/messaging_flutter/android/build.gradle.kts`
#### 테스트 작성
Native unit test를 직접 추가한다.
#### 중간 검증
```sh
cd apps/flutter-test/android && ./gradlew testDebugUnitTest
```
### [ANDROID-2] Local Android preflight 기록
#### 문제
현재 local `./gradlew testDebugUnitTest`는 Android SDK path가 없어 실패했다. [apps/flutter-test/android/local.properties](/config/workspace/nexo/apps/flutter-test/android/local.properties:1)는 `flutter.sdk`만 갖고 있고 `sdk.dir`이 없다.
Observed:
```text
SDK location not found. Define a valid SDK location with an ANDROID_HOME environment variable or by setting the sdk.dir path in your project's local properties file
```
#### 해결 방법
구현자는 local 환경을 repo에 설치하지 않는다. `command -v adb`, `command -v emulator`, `command -v sdkmanager`, `flutter doctor -v`, `./gradlew testDebugUnitTest`를 실행하고 실제 출력을 review stub에 기록한다. `ANDROID_HOME`이 준비되어 있으면 Gradle test를 통과시킨다.
#### 수정 파일 및 체크리스트
- [ ] 소스 파일 변경 없음. 검증 결과 기록만 수행한다.
#### 테스트 작성
테스트 작성 없음. 환경 preflight다.
#### 중간 검증
```sh
command -v adb
command -v emulator
command -v sdkmanager
flutter doctor -v
cd apps/flutter-test/android && ./gradlew testDebugUnitTest
```
### [ANDROID-3] Remote/device verification handoff
#### 문제
[packages/messaging_flutter/docs/android-test-environment.md](/config/workspace/nexo/packages/messaging_flutter/docs/android-test-environment.md:73)는 remote check path를 제공하지만 실제 실행은 외부 SSH/device 조건이 필요하다.
Before:
```markdown
73 ## Run The Checks
77 cd "$HOME/tmp/nexo-codex-test/packages/messaging_flutter"
```
#### 해결 방법
remote host 접근이 가능하면 guide의 sync/emulator/check commands를 그대로 실행한다. 접근이 불가능하면 `사용자 리뷰 요청`에 필요한 준비 사항을 남긴다: Android SDK 또는 remote SSH availability, emulator/device, Firebase config 여부.
#### 수정 파일 및 체크리스트
- [ ] 필요 시 `packages/messaging_flutter/docs/android-test-environment.md`의 command drift만 수정한다.
#### 테스트 작성
새 테스트 없음. 외부 환경 검증이다.
#### 중간 검증
```sh
cd apps/flutter-test && flutter test integration_test -d emulator-5554
cd apps/flutter-test/android && ./gradlew testDebugUnitTest
```
## 수정 파일 요약
| 파일 | 항목 |
| --- | --- |
| `packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt` | ANDROID-1 |
| `packages/messaging_flutter/android/build.gradle.kts` | ANDROID-1, only if a test dependency is justified |
| `packages/messaging_flutter/docs/android-test-environment.md` | ANDROID-3, only if command drift is found |
## 최종 검증
```sh
command -v adb
command -v emulator
command -v sdkmanager
flutter doctor -v
cd apps/flutter-test/android && ./gradlew testDebugUnitTest
cd apps/flutter-test && flutter test integration_test -d emulator-5554
git diff --check -- packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt packages/messaging_flutter/android/build.gradle.kts packages/messaging_flutter/docs/android-test-environment.md
```
Local Android SDK/device 부재는 PASS가 아니라 environment blocker다. blocker가 남으면 `사용자 리뷰 요청`을 채우고 code-review가 stop 여부를 판단하게 한다.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -18,6 +18,10 @@ This app is not a nexo product app and is not the original Mattermost app. The
original Mattermost app belongs under `apps/mattermost/`; app-specific product
UI and business logic belong in consuming apps outside this repository.
The app is a plugin API consumer and verification host. Native push handling,
ACK delivery, token/key storage, notification rendering, inline reply, and
dismiss handling remain owned by `packages/messaging_flutter`.
## Structure
```text
@ -67,11 +71,13 @@ Run Android native unit tests from `apps/flutter-test/android`:
./gradlew testDebugUnitTest
```
Android Gradle tests require a configured Android SDK through `ANDROID_HOME` or
`apps/flutter-test/android/local.properties`.
Android Gradle tests require a configured Android SDK through `ANDROID_HOME`,
`ANDROID_SDK_ROOT`, or `apps/flutter-test/android/local.properties`.
When the local machine does not have a usable Android SDK or emulator, use the
repository-level remote environment guide:
When the local machine does not have a usable Android SDK, device, or emulator,
the expected local result is an environment failure rather than a product
failure. Use the repository-level remote environment guide for Android
integration and native unit verification:
[`../../packages/messaging_flutter/docs/android-test-environment.md`](../../packages/messaging_flutter/docs/android-test-environment.md).
## Maintenance Rules

View file

@ -8,6 +8,14 @@ each consuming app: native FCM handling, notification display, notification tap
events, inline replies, dismiss handling, ACK delivery, device-token storage,
auth-token storage, and server signing-key storage.
## SDK Ownership Boundary
This package is the nexo-owned embedded messaging SDK. Its Android
implementation follows the nexo server/push contract, Android and FCM platform
changes, and Flutter plugin registration behavior. It does not follow or mirror
the Mattermost mobile app source tree; that app remains under `apps/mattermost/`
as an upstream clone.
## Platform Status
| Platform | Status | Notes |
@ -237,7 +245,27 @@ Use the repository tests as the source of truth for plugin behavior:
## Manual Smoke Checklist
Use this checklist before promoting a plugin change to a consuming app:
Use this checklist before promoting a plugin change to a consuming app. These
checks require external Firebase and nexo server infrastructure, so they are not
expected to pass in a headless repository test run.
Prerequisites:
- Firebase is configured for the host app with `android/app/google-services.json`.
- The host app has called `NexoMessagingPlugin.instance.initialize()`.
- The test user has a server URL, auth token, and signing key saved through the
plugin API.
- The nexo server can send a push payload with `ack_id`, `server_url` or
resolvable `server_id`, `channel_id`, and a valid signature.
Evidence to capture:
- `logcat` lines from `NexoFirebaseMessagingService` for receipt, ACK, and
signature handling.
- A screenshot or device recording of the displayed notification.
- Server-side ACK/reply request logs showing response status.
- Host app UI evidence for `onNotificationOpened`, `onNavigateToChannel`, and
`onNavigateToThread` routing.
- [ ] FCM payload wakes `NexoFirebaseMessagingService`.
- [ ] Valid nexo push payload passes signing-key verification.

View file

@ -19,6 +19,23 @@ export ANDROID_HOME="$HOME/Library/Android/sdk"
export ANDROID_SDK_ROOT="$ANDROID_HOME"
```
## Local Android Preflight
Before using the remote host, check whether the local machine already has the
Android toolchain needed for integration and Gradle unit tests:
```sh
flutter doctor -v
command -v adb
command -v emulator
command -v sdkmanager
```
`./gradlew testDebugUnitTest` requires either `ANDROID_HOME`/`ANDROID_SDK_ROOT`
or a valid `sdk.dir` entry in `apps/flutter-test/android/local.properties`. If
the command fails with `SDK location not found`, treat it as a local environment
blocker and use the remote flow below instead of changing repository files.
## Sync A Working Copy
From the local repository root, copy the current working tree to an isolated