<!-- task=m-mattermost-nexo-messaging-alignment plan=1 tag=REVIEW_SMOKE -->

# Plan - REVIEW_SMOKE

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

이 계획의 구현 완료는 active `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 내용으로 채우는 것까지 포함한다. 검증 명령을 실행하고 실제 stdout/stderr 또는 redacted 로그 경로를 기록한 뒤 active 파일을 유지한 채 리뷰 준비를 보고한다.

외부 Android runner, private secret, 원격 sibling workspace, emulator, Mattermost API가 다시 차단되면 `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 정확한 결정 필요 사항, 근거, 실행한 명령과 출력, 재개 조건을 채우고 멈춘다. 구현 에이전트는 `USER_REVIEW.md`, archive log, `complete.log`를 만들지 않는다.

## 배경

이 작업은 `USER_REVIEW` stop state에서 재개한다. 사용자가 원격 runner에 직접 동기화하라고 결정했고, 이후 `~/works/nomadcode`, `~/works/nexo`, `~/works/proto-socket`, private inputs, credential asset, emulator가 준비된 것을 확인했다. 남은 일은 원격 Android runner에서 server-generated signed push smoke를 실제로 실행하고 redacted evidence로 milestone 상태를 갱신하는 것이다.

## 사용자 리뷰 요청 흐름

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

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/external-integration/PHASE.md`
- `agent-roadmap/phase/external-integration/milestones/mattermost-nexo-messaging-alignment.md`
- `agent-task/m-mattermost-nexo-messaging-alignment/user_review_0.log`
- `agent-task/m-mattermost-nexo-messaging-alignment/plan_cloud_G07_0.log`
- `agent-task/m-mattermost-nexo-messaging-alignment/code_review_cloud_G07_0.log`
- `agent-ops/rules/private/rules.md`
- `docs/test-environments.md`
- `apps/client/.gitignore`
- `apps/client/pubspec.yaml`
- `apps/client/lib/src/integrations/mattermost/mattermost_auth_service.dart`
- `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/test/integrations/mattermost_push_host_integration_test.dart`
- `../nexo/packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart`
- `../nexo/packages/messaging_flutter/lib/src/notification_opened_event.dart`
- `../nexo/packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoFirebaseMessagingService.kt`
- `../nexo/packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPlugin.kt`
- `../nexo/packages/messaging_flutter/README.md`

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

- `user_review_0.log:26`의 checkout 부재 blocker는 해소됨. 원격 preflight에서 sibling repo, private input, credential asset, `emulator-5554`, `flutter test`, `flutter analyze --no-fatal-infos`를 확인했다.
- Real FCM receipt, valid signature, notification display, opened routing, inline reply, dismiss, ACK는 local test로 검증되지 않는다. `docs/test-environments.md:62` 이하의 remote smoke가 필요한 영역이다.
- Invalid/unsigned negative controls는 direct push-proxy 접근 가능 여부에 의존한다. 접근 불가 시 Evidence Checklist에서 `N/A`로 기록한다.

### 심볼 참조

- 변경 심볼: 없음.
- 확인한 관련 심볼: `MattermostPushClient.setAuthToken(..., identifier:)`, `serverId`, `serverIdentifier`, `mattermost_credentials.json`.
- 현재 call/reference: `mattermost_auth_service.dart`, `mattermost_push_plugin_client.dart`, `mattermost_push_host_integration_test.dart`, `docs/test-environments.md`, `packages/contracts/notes/flutter-core-api-candidates.md`, `../nexo/packages/messaging_flutter`의 Dart/Android 구현.

### 분할 판단

Split decision policy was evaluated before choosing plan files. Single plan is appropriate because this follow-up is one external smoke/evidence unit: remote readiness, app run, Mattermost trigger, evidence classification, and roadmap note must describe the same run. Splitting setup/run/evidence would make the evidence harder to correlate and would not produce a useful partial `complete.log`.

### 범위 결정 근거

Do not implement core Mattermost REST message sending here; the trigger uses private smoke inputs only. Do not modify native push implementation under `apps/client/android`; `../nexo/packages/messaging_flutter` remains the owner. Do not commit raw tokens, passwords, FCM tokens, signing keys, push signatures, or raw log files. Do not implement iOS/macOS runtime.

### 빌드 등급

Build lane: `cloud-G07`. Rationale: this is terminal/external smoke work with SSH, emulator, private inputs, command output redaction, Mattermost server-generated push delivery, and verification trust recovery after a previous environment-blocked FAIL.

## 구현 체크리스트

- [ ] [REVIEW_SMOKE-1] 원격 Android runner에서 sibling workspace, private inputs, credential asset, emulator, `flutter pub get`, `flutter test`, `flutter analyze --no-fatal-infos`를 재확인하고 실제 출력 또는 redacted 요약을 기록한다.
- [ ] [REVIEW_SMOKE-2] `flutter run -d emulator-5554 --no-resident`로 앱을 실행해 로그인, signing key 저장, FCM device token 등록 로그를 redaction 상태로 확보한다.
- [ ] [REVIEW_SMOKE-3] `.env.mattermost.local`의 Mattermost trigger 환경으로 정상 Mattermost post를 생성해 server-generated signed push를 유발하고, logcat/notification evidence를 PASS/PARTIAL/BLOCKED/N/A로 기록한다.
- [ ] [REVIEW_SMOKE-4] smoke evidence에 따라 `agent-roadmap/phase/external-integration/milestones/mattermost-nexo-messaging-alignment.md`의 `[signed-smoke-refresh]`와 완료 리뷰 후보를 갱신하거나 미충족 항목을 redacted 작업 컨텍스트로 남긴다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [REVIEW_SMOKE-1] Remote Readiness Recheck

### 문제

Previous review failed because `agent-task/m-mattermost-nexo-messaging-alignment/user_review_0.log:26` recorded no usable remote `nomadcode` checkout. The follow-up must prove that blocker is gone before relying on smoke evidence.

### 해결 방법

Run deterministic remote preflight. Record file existence and permissions only; do not print file contents.

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

- [ ] `CODE_REVIEW-cloud-G07.md` records preflight output.
- [ ] No raw secret or credential content is pasted.

### 테스트 작성

테스트 추가 없음. This is remote environment preflight.

### 중간 검증

```bash
ssh toki@toki-labs.com 'source ~/.nomadcode-env.sh; set -e; cd ~/works/nomadcode; test -d apps/client; test -d ../nexo/packages/messaging_flutter; test -d ../proto-socket/dart; test -f agent-ops/rules/private/testing-secrets.md; test -f .env.mattermost.local; test -f apps/client/assets/mattermost_credentials.json; stat -f "%Sp %N" agent-ops/rules/private/testing-secrets.md .env.mattermost.local apps/client/assets/mattermost_credentials.json; command -v flutter; command -v adb; adb devices; cd apps/client; flutter pub get; flutter test; flutter analyze --no-fatal-infos'
```

Expected: private files exist with restrictive permissions, `emulator-5554` is listed, `flutter pub get` succeeds, `flutter test` passes, and analyze exits 0 with the known `avoid_print` info only.

## [REVIEW_SMOKE-2] App Launch And Auth Handoff

### 문제

`docs/test-environments.md:122` requires the Android app run to prove Firebase initialization, Mattermost login, signing key storage, and FCM registration before triggering the push. Without this, the server-generated push can fail for setup reasons rather than plugin behavior.

### 해결 방법

Clear logcat, run the app on the remote emulator, save raw logs only under `/tmp`, and create a redacted log for task evidence.

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

- [ ] Remote app run output is saved under `/tmp`.
- [ ] `CODE_REVIEW-cloud-G07.md` records only redacted setup evidence.
- [ ] Required setup lines from `docs/test-environments.md:134` are PASS/PARTIAL/BLOCKED.

### 테스트 작성

테스트 추가 없음. This is the device smoke setup run.

### 중간 검증

```bash
ssh toki@toki-labs.com 'bash -lc '"'"'set -euo pipefail; source ~/.nomadcode-env.sh; cd ~/works/nomadcode/apps/client; adb -s emulator-5554 logcat -c; flutter run -d emulator-5554 --no-resident 2>&1 | tee /tmp/nomadcode-mattermost-run.raw.log; perl -pe "s#https?://[^[:space:]\\\"]+#<redacted-url>#g; s/android_rn-v2:[A-Za-z0-9:_-]+/android_rn-v2:<redacted>/g; s/New FCM token: .*/New FCM token: <redacted>/g; s/(signature=)[^ ,]+/\${1}<redacted>/g; s/[A-Za-z0-9_-]{80,}/<redacted-long>/g" /tmp/nomadcode-mattermost-run.raw.log > /tmp/nomadcode-mattermost-run.redacted.log; grep -E "FirebaseApp initialization successful|MattermostAuth|PushNotification|FCM device token registered" /tmp/nomadcode-mattermost-run.redacted.log || true'"'"''
```

Expected: app launch completes, login/signing-key/device-registration lines are present or any failure is explicit and redacted.

## [REVIEW_SMOKE-3] Server-Generated Push And Evidence

### 문제

`docs/test-environments.md:146` requires a normal Mattermost post so the server and push-proxy generate the signed FCM path. A manually crafted positive push is not valid evidence.

### 해결 방법

Use only `.env.mattermost.local` on the remote host to create a Mattermost post. Capture redacted logcat and notification evidence. If direct negative-control push-proxy access is unavailable, mark invalid/unsigned drop as `N/A`.

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

- [ ] Mattermost post trigger exits with HTTP 201 or records exact redacted failure.
- [ ] Redacted logcat includes FCM receipt, signature outcome, ACK, notification event, opened/reply/dismiss evidence where available.
- [ ] Evidence Checklist uses only PASS, PARTIAL, BLOCKED, or N/A.
- [ ] Raw signatures, tokens, passwords, URLs, FCM tokens, and raw payloads are not pasted into tracked files.

### 테스트 작성

테스트 추가 없음. This is the external smoke required by the milestone.

### 중간 검증

```bash
ssh toki@toki-labs.com 'bash -s' <<'REMOTE'
set -euo pipefail
source ~/.nomadcode-env.sh
cd ~/works/nomadcode
set -a
source .env.mattermost.local
set +a
adb -s emulator-5554 logcat -c
message="nomadcode signed push smoke $(date -u +%Y-%m-%dT%H:%M:%SZ)"
python3 - "$MATTERMOST_TEST_CHANNEL_ID" "$message" > /tmp/nomadcode-mm-post-body.json <<'PY'
import json, sys
print(json.dumps({"channel_id": sys.argv[1], "message": sys.argv[2]}))
PY
status=$(curl -sS -o /tmp/nomadcode-mm-post-response.json -w "%{http_code}" \
  -H "Authorization: Bearer ${MATTERMOST_TOKEN}" \
  -H "Content-Type: application/json" \
  --data @/tmp/nomadcode-mm-post-body.json \
  "${MATTERMOST_BASE_URL}/api/v4/posts")
echo "post-http-status=${status}"
sleep 15
adb -s emulator-5554 logcat -d -v time > /tmp/nomadcode-mm-smoke.raw.log
perl -pe 's#https?://[^[:space:]"]+#<redacted-url>#g; s/android_rn-v2:[A-Za-z0-9:_-]+/android_rn-v2:<redacted>/g; s/New FCM token: .*/New FCM token: <redacted>/g; s/(signature=)[^ ,]+/${1}<redacted>/g; s/[A-Za-z0-9_-]{80,}/<redacted-long>/g' \
  /tmp/nomadcode-mm-smoke.raw.log > /tmp/nomadcode-mm-smoke.redacted.log
grep -E 'NexoFCM|ReceiptDelivery|Notification skipped|Sending notification event|MattermostHost|PushNotification' /tmp/nomadcode-mm-smoke.redacted.log || true
adb -s emulator-5554 shell dumpsys notification > /tmp/nomadcode-mm-notification.raw.txt || true
perl -pe 's#https?://[^[:space:]"]+#<redacted-url>#g; s/android_rn-v2:[A-Za-z0-9:_-]+/android_rn-v2:<redacted>/g; s/[A-Za-z0-9_-]{80,}/<redacted-long>/g' \
  /tmp/nomadcode-mm-notification.raw.txt > /tmp/nomadcode-mm-notification.redacted.txt || true
REMOTE
```

Expected: `post-http-status=201`; redacted logcat provides enough evidence to classify the checklist at `docs/test-environments.md:213`. If notification interaction requires manual emulator UI, record the exact action and redacted log/dumpsys evidence.

## [REVIEW_SMOKE-4] Roadmap Evidence Update

### 문제

The milestone task at `agent-roadmap/phase/external-integration/milestones/mattermost-nexo-messaging-alignment.md:36` remains unchecked until smoke evidence is recorded. It must not move to `[검토중]` unless the required evidence is actually satisfied.

### 해결 방법

Update the milestone with a short redacted evidence note. If the evidence checklist passes except optional negative controls/thread navigation, mark `[signed-smoke-refresh]` complete and set completion review to `[검토중]`; otherwise keep `[진행중]`, leave the task unchecked, and record the blocked/partial items.

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

- [ ] `agent-roadmap/phase/external-integration/milestones/mattermost-nexo-messaging-alignment.md` reflects actual evidence.
- [ ] Evidence note contains no raw private value.
- [ ] `git diff --check` is clean.

### 테스트 작성

테스트 추가 없음. This is roadmap evidence documentation.

### 중간 검증

```bash
git diff --check
```

Expected: no whitespace errors.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `agent-roadmap/phase/external-integration/milestones/mattermost-nexo-messaging-alignment.md` | REVIEW_SMOKE-4 |
| `agent-task/m-mattermost-nexo-messaging-alignment/CODE_REVIEW-cloud-G07.md` | REVIEW_SMOKE-1, REVIEW_SMOKE-2, REVIEW_SMOKE-3, REVIEW_SMOKE-4 |
| remote ignored `~/works/nomadcode/apps/client/assets/mattermost_credentials.json` | REVIEW_SMOKE-1, REVIEW_SMOKE-2 |
| remote ignored `~/works/nomadcode/.env.mattermost.local` | REVIEW_SMOKE-1, REVIEW_SMOKE-3 |

## 최종 검증

```bash
ssh toki@toki-labs.com 'source ~/.nomadcode-env.sh; cd ~/works/nomadcode/apps/client; flutter test'
ssh toki@toki-labs.com 'source ~/.nomadcode-env.sh; cd ~/works/nomadcode/apps/client; flutter analyze --no-fatal-infos'
git diff --check
```

Expected: remote Flutter tests pass; remote analyze exits 0 with known `avoid_print` info only; `git diff --check` is clean. All smoke outputs and evidence classification must be pasted into `CODE_REVIEW-cloud-G07.md` with private values redacted or with redacted remote log paths.

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