<!-- task=m-usb-install/01_adb_bundle plan=1 tag=REVIEW_API -->

# Plan - REVIEW_API

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

구현이 끝나면 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 검증을 실행하고, active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log` 작성, task archive 이동은 code-review 전용이다. 사용자만 결정할 수 있는 외부 환경, secret, 범위 충돌로 막히면 리뷰 stub의 `사용자 리뷰 요청`에 정확한 증거와 재개 조건을 남기고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니라 검증 보완 대상으로 처리한다.

## 배경

1차 구현은 macOS bundle에 `adb-runtime`을 만들었지만, build phase가 현재 머신의 임의 `adb`를 복사하고 누락 시 성공으로 넘어간다. `source.properties`에는 `Pkg.Revision=35.0.0`이 기록되어 있으므로 실제 복사된 바이너리도 같은 pinned revision이어야 한다. 이 follow-up은 bundle runtime 생성이 환경 우연이 아니라 빌드 계약으로 강제되게 고친다.

## 사용자 리뷰 요청 흐름

구현 중 차단 사유는 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 프롬프트는 금지이며, code-review가 요청 타당성과 `USER_REVIEW.md` 작성 여부를 판단한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/usable-mvp/milestones/usb-install.md`
- Task ids:
  - `adb-bundle`: macOS app bundle에 pin된 최소 ADB subset을 포함한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/project/domain/macos-platform/rules.md`
- `agent-ops/rules/project/domain/device-console/rules.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-test/local/rules.md`
- `agent-test/local/device-console-smoke.md`
- `agent-test/local/macos-platform-smoke.md`
- `agent-test/local/macos-remote-build-smoke.md`
- `agent-task/m-usb-install/01_adb_bundle/plan_cloud_G07_0.log`
- `agent-task/m-usb-install/01_adb_bundle/code_review_cloud_G07_0.log`
- `.gitignore`
- `macos/Runner.xcodeproj/project.pbxproj`
- `macos/Runner/Resources/adb-runtime/NOTICE.txt`
- `macos/Runner/Resources/adb-runtime/source.properties`
- `lib/src/services/adb_service.dart`
- `test/adb_service_test.dart`

### 테스트 환경 규칙

- test_env: `local`
- env rules: `agent-test/local/rules.md` present and read. 기본 완료 evidence는 standard remote Mac runner `toki@toki-labs.com` checkout `$HOME/docker/services/code-server/data/volume/workspace/appsok`.
- matched profiles:
  - `agent-test/local/device-console-smoke.md`: `flutter analyze`, `flutter test`; ADB runtime evidence는 remote runner 기준.
  - `agent-test/local/macos-platform-smoke.md`: macOS platform 변경 시 remote `flutter build macos`.
  - `agent-test/local/macos-remote-build-smoke.md`: remote checkout 상태, Xcode, signing identity, `flutter build macos`.
- 적용 명령:
  - `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && git status --short --branch'`
  - `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter pub get'`
  - `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter analyze'`
  - `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter test'`
  - `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter build macos'`
  - `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && test -x build/macos/Build/Products/Release/AppSok.app/Contents/Resources/adb-runtime/adb'`
  - `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && build/macos/Build/Products/Release/AppSok.app/Contents/Resources/adb-runtime/adb version'`
  - `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && otool -L build/macos/Build/Products/Release/AppSok.app/Contents/Resources/adb-runtime/adb'`
- `<확인 필요>` 값 없음. Notary credential은 이 작업 범위가 아니며 가능으로 단정하지 않는다.

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

- Build phase missing/mismatched ADB behavior: Dart unit test로 커버되지 않는다. remote `flutter build macos`와 pinned version shell check evidence가 필요하다.
- `AdbRuntimeConfig` path behavior: 이번 follow-up에서 path contract를 바꾸지 않으므로 기존 `test/adb_service_test.dart`가 충분하다.
- Sandbox USB execution: 이번 follow-up의 직접 범위가 아니며, runtime smoke는 bundled `adb version`과 `otool -L`까지만 요구한다.

### 심볼 참조

- Renamed/removed symbols: none.
- Call sites for ADB runtime entry points: `lib/src/services/adb_service.dart` only. 이번 follow-up은 Dart API를 바꾸지 않는다.

### 분할 판단

- split decision policy evaluated.
- Shared task group: `agent-task/m-usb-install/`
- Current subtask: `01_adb_bundle`, no predecessor index in directory name.
- Sibling `02+01_adb_diagnostics` still depends on index `01`; this follow-up must remain in `01_adb_bundle` until PASS and `complete.log` creation.

### 범위 결정 근거

- Include: `Copy ADB Runtime` build phase의 missing/mismatch failure, pinned revision comparison, metadata consistency, deterministic remote evidence.
- Exclude: settings UI diagnostics, `AdbService` API changes, wireless ADB, split APK/APKS/AAB, notarization, USB install smoke beyond bundled `adb version`.
- Keep the final bundle path contract as `AppSok.app/Contents/Resources/adb-runtime/adb`.

### 빌드 등급

- `cloud-G07`: Xcode shell build phase, external `adb` process output parsing, remote Mac build evidence, and verification trust recovery가 중심이다.

## 구현 체크리스트

- [ ] `Copy ADB Runtime` build phase가 `source.properties`의 `Pkg.Revision`과 실제 `adb version`을 비교하고, missing 또는 mismatch를 build failure로 처리한다.
- [ ] pinned metadata와 copied runtime 산출물이 remote Mac runner에서 일치함을 검증한다.
- [ ] `flutter analyze`, `flutter test`, `flutter build macos`를 remote Mac runner checkout에서 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

1. `01_adb_bundle` follow-up을 완료한다.
2. PASS 후 code-review가 `complete.log`를 만들면 `02+01_adb_diagnostics`를 시작할 수 있다.

### [REVIEW_API-1] Enforce Pinned ADB Runtime

#### 문제

- `macos/Runner.xcodeproj/project.pbxproj:340`의 build phase는 `ANDROID_SDK_ROOT`, `ANDROID_HOME`, 또는 `PATH`에서 찾은 임의 `adb`를 복사한다.
- `macos/Runner/Resources/adb-runtime/source.properties:2`는 `Pkg.Revision=35.0.0`을 선언하지만, phase는 복사한 바이너리의 `adb version`이 `35.0.0`인지 검증하지 않는다.
- 같은 phase는 `adb`가 없을 때 warning 후 `exit 0`으로 끝나므로, bundle에 `adb-runtime/adb`가 없어도 `flutter build macos`가 성공할 수 있다.

#### 해결 방법

Before:

```text
macos/Runner.xcodeproj/project.pbxproj:340
if [ -z "$ADB_BIN" ]; then
  echo "warning: adb not found — skipping bundle copy. Install Android SDK platform-tools and set ANDROID_SDK_ROOT."
  exit 0
fi
```

After:

```text
macos/Runner.xcodeproj/project.pbxproj
EXPECTED_REVISION="$(awk -F= '/^Pkg.Revision=/ {print $2}' "${SRCROOT}/Runner/Resources/adb-runtime/source.properties")"
ACTUAL_REVISION="$("$ADB_BIN" version | awk '/^Version / {print $2}' | cut -d- -f1)"
missing adb or revision mismatch prints an error to stderr and exits non-zero.
```

- Keep the existing shell phase or refactor only as much as needed inside `project.pbxproj`; do not change `AdbRuntimeConfig.effectiveBundledAdbPath`.
- Parse expected revision from `source.properties`, not from a duplicated literal in the shell script.
- If no executable `adb` is found, fail the build with a clear error.
- If copied `adb version` does not match `Pkg.Revision`, fail the build with expected/actual values.
- Ensure the phase reruns when needed. Prefer `alwaysOutOfDate = 1;` for this dynamic SDK lookup, or add sufficient `inputPaths` and `outputPaths` for metadata and copied artifacts if a deterministic alternative is chosen.
- Keep `NOTICE.txt` and `source.properties` consistent with the enforced source and revision.

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

- [ ] `macos/Runner.xcodeproj/project.pbxproj`: update `Copy ADB Runtime` shell script to fail on missing adb and revision mismatch.
- [ ] `macos/Runner.xcodeproj/project.pbxproj`: make the dynamic copy phase rerun reliably or declare complete inputs/outputs.
- [ ] `macos/Runner/Resources/adb-runtime/source.properties`: keep `Pkg.Revision` as the single pinned revision source.
- [ ] `macos/Runner/Resources/adb-runtime/NOTICE.txt`: keep the pinned version statement aligned with `source.properties`.

#### 테스트 작성

- New Dart unit tests are not required because Dart path resolution does not change.
- Remote smoke evidence is required because the behavior is in an Xcode shell build phase and copied macOS binary.

#### 중간 검증

```bash
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && ! rg --sort path "skipping bundle copy|adb not found.*exit 0" macos/Runner.xcodeproj/project.pbxproj'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter build macos'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && test -x build/macos/Build/Products/Release/AppSok.app/Contents/Resources/adb-runtime/adb'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && EXPECTED="$(awk -F= "/^Pkg.Revision=/ {print \\$2}" macos/Runner/Resources/adb-runtime/source.properties)" && ACTUAL="$(build/macos/Build/Products/Release/AppSok.app/Contents/Resources/adb-runtime/adb version | awk "/^Version / {print \\$2}" | cut -d- -f1)" && test "$ACTUAL" = "$EXPECTED" && printf "adb runtime version %s matches source.properties\n" "$ACTUAL"'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && otool -L build/macos/Build/Products/Release/AppSok.app/Contents/Resources/adb-runtime/adb'
```

Expected: no silent skip branch remains; build succeeds; bundled `adb` exists and is executable; actual version equals `Pkg.Revision`; dylib references are bundled or intentionally system-resolvable.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `macos/Runner.xcodeproj/project.pbxproj` | REVIEW_API-1 |
| `macos/Runner/Resources/adb-runtime/source.properties` | REVIEW_API-1 |
| `macos/Runner/Resources/adb-runtime/NOTICE.txt` | REVIEW_API-1 |

## 최종 검증

```bash
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && git status --short --branch'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter pub get'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter analyze'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter test'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && ! rg --sort path "skipping bundle copy|adb not found.*exit 0" macos/Runner.xcodeproj/project.pbxproj'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter build macos'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && test -x build/macos/Build/Products/Release/AppSok.app/Contents/Resources/adb-runtime/adb'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && EXPECTED="$(awk -F= "/^Pkg.Revision=/ {print \\$2}" macos/Runner/Resources/adb-runtime/source.properties)" && ACTUAL="$(build/macos/Build/Products/Release/AppSok.app/Contents/Resources/adb-runtime/adb version | awk "/^Version / {print \\$2}" | cut -d- -f1)" && test "$ACTUAL" = "$EXPECTED" && printf "adb runtime version %s matches source.properties\n" "$ACTUAL"'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && build/macos/Build/Products/Release/AppSok.app/Contents/Resources/adb-runtime/adb version'
zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && otool -L build/macos/Build/Products/Release/AppSok.app/Contents/Resources/adb-runtime/adb'
```

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