<!-- task=m-mattermost-nexo-messaging-alignment/02+01_signed_smoke 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-30
task=m-mattermost-nexo-messaging-alignment/02+01_signed_smoke, plan=0, tag=TEST

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G08.md` → `code_review_cloud_G08_N.log`, `PLAN-cloud-G08.md` → `plan_cloud_G08_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-mattermost-nexo-messaging-alignment/02+01_signed_smoke/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [TEST-1] Remote Android baseline | [x] |
| [TEST-2] Server-generated signed push smoke | [x] |
| [TEST-3] Evidence Checklist update | [x] |

## 구현 체크리스트

- [x] [TEST-1] `01_runner_preflight` 완료를 확인하고 remote Android baseline commands를 실행한다.
- [x] [TEST-2] Mattermost server-generated signed push를 발생시키고 redacted FCM/notification evidence를 수집한다.
- [x] [TEST-3] `agent-test/local/mattermost-server-generated-push-smoke.md`의 Evidence Checklist를 PASS/PARTIAL/BLOCKED/N/A 결과로 갱신한다. 검증: `agent-test/local/mattermost-server-generated-push-smoke.md`의 Evidence Checklist가 PASS/PARTIAL/BLOCKED로 기록된다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G08_M.log`로 아카이브한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/m-mattermost-nexo-messaging-alignment/02+01_signed_smoke/`를 `agent-task/archive/YYYY/MM/m-mattermost-nexo-messaging-alignment/02+01_signed_smoke/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-mattermost-nexo-messaging-alignment/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G08.md`와 `CODE_REVIEW-cloud-G08.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로 이동한다.

## 계획 대비 변경 사항

- DEVICE_ID는 plan 기본값 `emulator-5554` 그대로 사용했다. preflight device id와 동일해 대체하지 않았다.
- 검증 대상 checkout은 plan의 fallback 분기대로 remote host의 `$HOME/works/nomadcode`(`/Users/toki/works/nomadcode`)에서 잡혔다. `/config/workspace/nomadcode`는 remote에 없다.
- Notification display / opened / inline reply / dismiss evidence를 얻으려면 app이 **background(프로세스 살아있음)** 상태여야 했다. 첫 trigger는 app이 foreground live engine 상태라 FCM이 `onMessageReceived`→Flutter event로만 라우팅되고 system notification이 게시되지 않았다. 또 `am force-stop` 후 trigger는 Android가 stopped-state app에 FCM을 전달하지 않아 수신 자체가 없었다. 그래서 정상 launch → `KEYCODE_HOME`으로 background → trigger 순서로 system notification을 게시시켰다. 이는 plan의 fixed 검증 명령 흐름에 reproduction을 위한 보조 adb 조작(`monkey` launch, `input keyevent HOME`, `cmd statusbar expand-notifications`, `input tap`, `input text`, `screencap`)을 더한 것이며, plan의 trigger/collect 계약 명령 자체는 그대로 실행했다.
- 추가로 consolidated redacted evidence 파일 `/tmp/nomadcode-mm-smoke-evidence.redacted.txt`를 runner에 작성했다. plan의 `/tmp/nomadcode-mm-smoke-logcat.redacted.txt`, `/tmp/nomadcode-mm-smoke-notification.redacted.txt`도 계약 명령으로 생성했다.

## 주요 설계 결정

- Positive path는 Mattermost REST `POST /api/v4/posts`로 생성한 server-generated DM만 사용했고 hand-built placeholder signature를 쓰지 않았다.
- Secret redaction: bot token/password/raw FCM token/signing key/signature를 tracked 파일과 응답에 기록하지 않았다. `android_rn-v2:` 등록 로그 라인은 prefix 뒤에 raw FCM token이 포함되어 있어 evidence에 prefix만 기록하고 token 본문은 제외했다. userId/sessionId도 evidence 파일에서 `<redacted>` 처리했다.
- `invalid / unsigned drop`(negative control)은 smoke 문서상 direct push-proxy access가 필요한 보조 검증이며 이번 runner에 push-proxy 직접 접근이 없어 `N/A`로 판정했다. server REST 경로는 항상 정식 서명을 하므로 positive 경로로는 negative control을 만들 수 없다.
- `thread navigation`은 smoke payload에 `root_id`가 없어(`rootId=null`) `N/A`로 판정했다.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 사용자 결정, 외부 환경 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. code-review가 이 내용을 검증해 `USER_REVIEW.md`를 작성한다._

- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 재개 조건: 없음

## 리뷰어를 위한 체크포인트

- `01_runner_preflight` complete log 의존성이 실제로 충족됐는지 확인한다.
- Positive push가 Mattermost server-generated REST post 경로였고 placeholder signature가 아니었는지 확인한다.
- Evidence Checklist 결과가 11개 row 모두 `PASS`, `PARTIAL`, `BLOCKED`, `N/A` 중 하나인지 확인한다.
- Review/result/doc에 raw token, password, FCM token, signing key, push payload signature가 없는지 확인한다.

## 검증 결과

_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.

### TEST-1 중간 검증

```bash
$ find agent-task -path '*/m-mattermost-nexo-messaging-alignment/01_runner_preflight/complete.log' -print -quit | grep .
agent-task/archive/2026/05/m-mattermost-nexo-messaging-alignment/01_runner_preflight/complete.log
```

```bash
$ ssh toki@toki-labs.com 'set -eu
if [ -d /config/workspace/nomadcode/.git ]; then
  cd /config/workspace/nomadcode
elif [ -d "$HOME/works/nomadcode/.git" ]; then
  cd "$HOME/works/nomadcode"
else
  echo "nomadcode checkout missing"
  exit 2
fi
REPO_DIR="$PWD"
zsh -lc "cd \"$REPO_DIR/apps/client\" && flutter pub get && flutter analyze --no-fatal-infos && flutter test && flutter build apk --debug && flutter devices"
'
# checkout resolved to /Users/toki/works/nomadcode ($HOME/works/nomadcode fallback)
Resolving dependencies...
Changed 3 dependencies!
[... pub SPM/KGP plugin warnings for nexo_messaging omitted ...]
Analyzing client...
   info • Don't invoke 'print' in production code ... • lib/src/integrations/mattermost/mattermost_auth_service.dart:27:7 • avoid_print
   [... 12 more avoid_print info issues in mattermost_auth_service.dart ...]
13 issues found. (ran in 5.1s)   # info-level only, no errors/warnings
00:00 +15: All tests passed!
Running Gradle task 'assembleDebug'...                             17.7s
✓ Built build/app/outputs/flutter-apk/app-debug.apk
Found 3 connected devices:
  sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64  • Android 14 (API 34) (emulator)
  macOS (desktop)             • macos         • darwin-arm64   • macOS 26.0.1 25A362 darwin-arm64
  Chrome (web)                • chrome        • web-javascript • Google Chrome 148.0.7778.215
# EXITCODE=0
```

### TEST-2 중간 검증

```bash
$ ssh toki@toki-labs.com 'set -eu
if [ -d /config/workspace/nomadcode/.git ]; then
  cd /config/workspace/nomadcode
elif [ -d "$HOME/works/nomadcode/.git" ]; then
  cd "$HOME/works/nomadcode"
else
  echo "nomadcode checkout missing"
  exit 2
fi
DEVICE_ID="${DEVICE_ID:-emulator-5554}"
REPO_DIR="$PWD"
zsh -lc "cd \"$REPO_DIR/apps/client\" && flutter run -d \"$DEVICE_ID\" --no-resident"
'
[... SPM/KGP plugin warnings omitted ...]
Launching lib/main.dart on sdk gphone64 arm64 in debug mode...
✓ Built build/app/outputs/flutter-apk/app-debug.apk
Installing build/app/outputs/flutter-apk/app-debug.apk...          571ms
I/flutter : Using the Impeller rendering backend (OpenGLES).
Syncing files to device sdk gphone64 arm64...
# EXITCODE=0

# Pre-trigger setup logcat (redacted) confirming login/signing-key/FCM registration:
I FirebaseApp: ... FirebaseApp initialization successful
I flutter : [MattermostAuth] Logging in to https://mattermost.toki-labs.com ...
I flutter : [MattermostAuth] Login OK - userId=<redacted> sessionId=<redacted>
I flutter : [MattermostAuth] Signing key stored.
I flutter : [MattermostAuth] FCM device token registered successfully.
I flutter : [MattermostAuth] Auto login & FCM registration complete.
```

```bash
$ : "${MM_BASE_URL:?export from agent-test/local/testing-secrets.md without printing the value}"
$ : "${MM_BOT_TOKEN:?export from agent-test/local/testing-secrets.md without printing the value}"
$ : "${MM_RECEIVER_EMAIL:?export from agent-test/local/testing-secrets.md without printing the value}"
$ python3 - <<'PY'
import datetime
import json
import os
import urllib.parse
import urllib.request

base = os.environ["MM_BASE_URL"].rstrip("/")
token = os.environ["MM_BOT_TOKEN"]
receiver = os.environ["MM_RECEIVER_EMAIL"]
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}

def req(method, path, data=None):
    body = None if data is None else json.dumps(data).encode()
    request = urllib.request.Request(base + path, data=body, headers=headers, method=method)
    with urllib.request.urlopen(request, timeout=30) as response:
        return json.load(response)

bot = req("GET", "/api/v4/users/me")
user = req("GET", "/api/v4/users/email/" + urllib.parse.quote(receiver, safe=""))
channel = req("POST", "/api/v4/channels/direct", [bot["id"], user["id"]])
message = "nomadcode signed push smoke " + datetime.datetime.utcnow().isoformat(timespec="seconds") + "Z"
post = req("POST", "/api/v4/posts", {"channel_id": channel["id"], "message": message})
print(json.dumps({"post_id": post.get("id"), "channel_id": channel.get("id"), "message_prefix": "nomadcode signed push smoke"}, ensure_ascii=False))
PY
{"post_id": "zaex9x1xubyk7d3agko76k37sr", "channel_id": "dii4pdizbfnyjcz9azyep3nkfo", "message_prefix": "nomadcode signed push smoke"}
# no token/password/signature printed. Additional triggers used the same path for the
# backgrounded notification-display / opened / dismiss / inline-reply phases (post_ids
# zihazf8..., o64b9rd..., ic1x1cm..., cga78zp...).
```

```bash
$ ssh toki@toki-labs.com 'set -eu
zsh -lc '"'"'
adb logcat -d |
  sed -E "s/(Bearer )[A-Za-z0-9._-]+/\1<redacted>/g; s/(token=)[A-Za-z0-9:_-]+/\1<redacted>/g; s/(signature=)[A-Za-z0-9=:+/_-]+/\1<redacted>/g" |
  grep -E "FirebaseApp|MattermostAuth|NexoFCM|ReceiptDelivery|Notification skipped|inline|dismiss|clear|android_rn-v2|notification" \
  > /tmp/nomadcode-mm-smoke-logcat.redacted.txt
adb shell dumpsys notification |
  sed -E "s/(Bearer )[A-Za-z0-9._-]+/\1<redacted>/g; s/(token=)[A-Za-z0-9:_-]+/\1<redacted>/g; s/(signature=)[A-Za-z0-9=:+/_-]+/\1<redacted>/g" \
  > /tmp/nomadcode-mm-smoke-notification.redacted.txt
printf "/tmp/nomadcode-mm-smoke-logcat.redacted.txt\n"
printf "/tmp/nomadcode-mm-smoke-notification.redacted.txt\n"
'"'"'
'
/tmp/nomadcode-mm-smoke-logcat.redacted.txt
/tmp/nomadcode-mm-smoke-notification.redacted.txt
# EXITCODE=0. Consolidated redacted evidence also written to
# /tmp/nomadcode-mm-smoke-evidence.redacted.txt (secret scan: CLEAN).
#
# Sanitized excerpts (full positive + interaction path):
#   FCM receipt:    NexoFCM onMessageReceived type=message channelId=dii4...nkfo ackId=<redacted>
#   signature ok:   NexoFCM "Sending notification event to Flutter: type=message" (no "signature verification failed")
#   ACK:            ReceiptDelivery Send ACK=<redacted> TYPE=message
#   display:        dumpsys NotificationRecord pkg=com.tokilabs.mattermost importance=4 category=msg
#                   title=@build text="nomadcode signed push smoke bgalive ..." actions=1 ("Reply")
#   opened/nav:     [PushNotification] Opened: channelId=dii4...nkfo rootId=null
#                   [MattermostHost] Navigate to channel: dii4...nkfo ; topResumedActivity=com.tokilabs.mattermost/.MainActivity
#   dismiss/clear:  NotifDismissService Dismiss notification id=<id> ; remaining tokilabs notifications=0
#   inline reply:   NotifReplyReceiver Reply SUCCESS + NomadNetwork POST /api/v4/posts ;
#                   channel latest post="nomadcode-inline-reply-smoke"
#   token prefix:   registration log shows android_rn-v2: prefix (raw FCM token redacted)
```

### TEST-3 중간 검증

```bash
$ rg --sort path '^\| (FCM payload wakes / receipt|valid server-generated signature 처리|invalid / unsigned drop|notification display|ACK request|opened event|channel navigation|thread navigation|inline reply|dismiss / clear|device token prefix) \| [^|]+ \| (PASS|PARTIAL|BLOCKED|N/A) \|' agent-test/local/mattermost-server-generated-push-smoke.md | wc -l
11
```

```bash
$ rg --sort path -n 'Bearer [A-Za-z0-9._-]+|[A-Za-z0-9_-]{120,}' agent-test/local/mattermost-server-generated-push-smoke.md
# (no output — exit 1)
```

### 최종 검증

```bash
$ find agent-task -path '*/m-mattermost-nexo-messaging-alignment/01_runner_preflight/complete.log' -print -quit | grep .
$ rg --sort path '^\| (FCM payload wakes / receipt|valid server-generated signature 처리|invalid / unsigned drop|notification display|ACK request|opened event|channel navigation|thread navigation|inline reply|dismiss / clear|device token prefix) \| [^|]+ \| (PASS|PARTIAL|BLOCKED|N/A) \|' agent-test/local/mattermost-server-generated-push-smoke.md | wc -l
$ rg --sort path -n 'Bearer [A-Za-z0-9._-]+|[A-Za-z0-9_-]{120,}' agent-test/local/mattermost-server-generated-push-smoke.md
agent-task/archive/2026/05/m-mattermost-nexo-messaging-alignment/01_runner_preflight/complete.log
11
# (secret scan: 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.

## 코드리뷰 결과

- 종합 판정: PASS
- 차원별 평가:
  - correctness: Pass
  - completeness: Pass
  - test coverage: Pass
  - API contract: Pass
  - code quality: Pass
  - plan deviation: Pass
  - verification trust: Pass
- 발견된 문제: 없음
- 다음 단계: PASS이므로 `complete.log` 작성 후 active task directory를 archive로 이동한다.
