<!-- task=m-jenkins-credential/02+01_app_wiring plan=1 tag=API -->

# Plan - API

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

이 작업은 `agent-task/m-jenkins-credential/01_session_foundation/complete.log`가 생긴 뒤 구현한다. 구현 후 active `CODE_REVIEW-local-G06.md`의 구현 에이전트 소유 섹션을 채워야 완료된다. 검증 명령은 계획에 적힌 대로 실행하고 실제 stdout/stderr를 붙여 넣은 뒤, active 파일을 그대로 둔 채 리뷰 준비를 보고한다. `코드리뷰 결과`, log archive, `complete.log`, task directory archive는 code-review 전용이다.

구현 중 사용자만 결정할 수 있는 범위 변경, 사용자 소유 secret/외부 Jenkins 준비, 또는 이 계획과 충돌하는 요구가 발견되면 사용자에게 직접 묻지 않는다. `CODE_REVIEW-local-G06.md`의 `사용자 리뷰 요청` 섹션에 결정 항목, 근거, 실행한 명령과 출력, 재개 조건을 기록하고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 닫을 수 있는 증거 공백은 사용자 리뷰 요청이 아니라 검증 결과 또는 계획 대비 변경 사항으로 기록한다.

## 배경

`Artifact 탐색과 다운로드`의 코드레벨 종료 검토에서 실제 앱 루트가 Jenkins artifact flow에 연결되지 않는 Required 이슈가 나왔다. 01 foundation이 저장 session과 credential 검증 API를 제공하면, 앱 루트는 이 foundation을 사용해 저장된 session이 있을 때 job/build/download loader를 `AppSokShell`에 연결해야 한다. 이 계획은 WebView token 발급 전체가 아니라 저장된 session 복원 성공 시 artifact browser가 production path에서 동작하는 최소 wiring을 다룬다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`에서 복사된 형식을 사용한다. 구현 에이전트는 직접 사용자 프롬프트, 채팅 선택지, `request_user_input`, `USER_REVIEW.md` 생성, log archive, `complete.log` 작성을 하지 않는다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/usable-mvp/milestones/jenkins-credential.md`
- Task ids:
  - `startup-check`: 앱 시작 시 Keychain credential을 읽고 Jenkins 사용자 확인 API로 조용히 검증한다.
  - `auth-state`: 검증 성공 시 로그인 화면을 건너뛰고 build 목록으로 진입한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/artifact-flow/rules.md`
- `agent-ops/rules/project/domain/app-shell/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/artifact-flow-smoke.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/usable-mvp/PHASE.md`
- `agent-roadmap/phase/usable-mvp/milestones/artifact-browser.md`
- `agent-roadmap/phase/usable-mvp/milestones/jenkins-credential.md`
- `pubspec.yaml`
- `lib/src/app.dart`
- `lib/src/features/app_shell.dart`
- `lib/src/features/builds/builds_page.dart`
- `lib/src/features/settings/settings_page.dart`
- `lib/src/models/jenkins_build.dart`
- `lib/src/models/pending_install.dart`
- `lib/src/services/artifact_staging_service.dart`
- `lib/src/services/jenkins_client.dart`
- `lib/src/services/token_store.dart`
- `test/artifact_staging_service_test.dart`
- `test/builds_page_test.dart`
- `test/jenkins_client_test.dart`
- `test/widget_test.dart`

### 테스트 환경 규칙

- test_env: local.
- `agent-test/local/rules.md` 존재 및 읽음.
- matched profile: `agent-test/local/artifact-flow-smoke.md` 읽음.
- 적용 명령: remote runner checkout에서 `flutter analyze`, `flutter test`; 보조로 현재 checkout `git diff --check`.
- 구조상 빈/skeleton 규칙 없음.
- `<확인 필요>` 값 없음.
- 실제 Jenkins endpoint/credential은 제공되지 않았으므로 mock HTTP와 stored-session test로 검증한다. 실제 Jenkins smoke는 sandbox endpoint가 제공될 때만 보조 검증이다.

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

- [app.dart](/config/workspace/appsok/lib/src/app.dart:15)의 기본 `AppSokApp` 경로가 artifact loader를 주입하지 않는 문제를 덮는 widget test가 없다. 이 작업에서 stored session + mock HTTP로 기본 앱 루트가 job list를 보여주는 회귀 테스트를 추가한다.
- artifact download URL 조립은 현재 production call site가 없어 테스트되지 않는다. 이 작업에서 build URL과 artifact relative path로 `.../artifact/<relativePath>` 요청을 만드는 service test를 추가한다.

### 심볼 참조

- 변경 예정 symbol: `AppSokApp` 생성자에 testable dependency injection 추가. call sites: [main.dart](/config/workspace/appsok/lib/main.dart:6), [widget_test.dart](/config/workspace/appsok/test/widget_test.dart:30), [widget_test.dart](/config/workspace/appsok/test/widget_test.dart:46), [widget_test.dart](/config/workspace/appsok/test/widget_test.dart:757).
- 변경 예정 symbol: `AppSokShell` loader/downloader props는 유지한다. 기존 test call sites는 `rg --sort path "AppSokShell\\(" lib test`로 재확인한다.
- 신규 symbol 후보: `JenkinsArtifactSession` 또는 동등한 service wrapper.
- 제거 예정 symbol: none.

### 분할 판단

- split decision policy를 적용했다.
- shared task group: `agent-task/m-jenkins-credential/`.
- 이 계획은 `02+01_app_wiring`이며 predecessor `01_session_foundation`의 `complete.log`가 필요하다.
- 현재 predecessor 상태: active `agent-task/m-jenkins-credential/01_session_foundation/PLAN-local-G05.md`가 생성될 예정이며 아직 `complete.log`는 없다.
- 분할 근거: 앱 root/widget wiring은 01의 storage/client API에 의존한다. 01이 PASS되지 않으면 이 계획 구현을 시작하지 않는다.

### 범위 결정 근거

- WebView 로그인, crumb/token 자동 발급, token 폐기 UX, logout UI는 제외한다. 이 계획은 저장된 session이 이미 있을 때 복원과 artifact flow wiring만 구현한다.
- `SettingsPage`의 최종 로그인 UX는 제외한다. 임시 manual token UX를 기본 UX로 만들면 roadmap의 WebView token 방향과 충돌할 수 있다.
- `DevicesPage`의 실제 ADB install 구현은 제외한다. handoff 이후는 `USB 설치와 device 선택` Milestone 책임이다.
- `BuildsPage` 내부 UI 상태는 기존 completed artifact-browser 범위로 유지한다.

### 빌드 등급

- build lane: `local-G06`.
- review lane: `local-G06`.
- 근거: app root, service wrapper, widget tests를 함께 건드리지만 범위와 검증이 bounded하다. 보안/인증 UX 전체가 아니라 stored-session wiring이다.

## 구현 체크리스트

- [ ] `01_session_foundation`의 `complete.log` 존재를 확인하고, 없으면 구현을 시작하지 않고 사용자 리뷰 요청이 아닌 선행 의존성 미충족으로 기록한다.
- [ ] 저장된 Jenkins session을 검증하고 job/build/download/stage/cleanup 함수를 제공하는 artifact session service를 추가한다.
- [ ] `AppSokApp` 기본 경로가 저장 session 검증 성공 시 `AppSokShell`에 production loader/downloader/stager/cleaner를 주입하도록 연결한다.
- [ ] stored session이 없거나 검증 실패한 경우 secret을 노출하지 않고 기존 연결 필요/비활성 상태를 유지한다.
- [ ] artifact URL 조립과 `AppSokApp` 기본 경로 회귀 테스트를 추가한다.
- [ ] `git diff --check`와 remote runner `flutter analyze`, `flutter test`를 실행해 결과를 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- 이 subtask directory는 `02+01_app_wiring`이므로 `01_session_foundation`에만 의존한다.
- 구현 전 `agent-task/m-jenkins-credential/01_session_foundation/complete.log` 또는 archive의 matching `agent-task/archive/*/*/m-jenkins-credential/01_session_foundation/complete.log`가 있어야 한다.
- 이 계획은 01 외 추가 의존성을 만들지 않는다.

### [API-1] Artifact session service

문제: [app_shell.dart](/config/workspace/appsok/lib/src/features/app_shell.dart:21)는 loader/downloader를 받을 수 있지만, production service가 없다. [jenkins_client.dart](/config/workspace/appsok/lib/src/services/jenkins_client.dart:111)는 `downloadArtifactStream`을 받는 URL이 필요하고, 현재 codebase에는 `BuildArtifact.relativePath`를 build URL 아래 artifact URL로 바꾸는 production call site가 없다.

해결 방법:

Before:

```dart
21  const AppSokShell({
22    super.key,
23    this.jobLoader,
24    this.buildLoader,
25    this.artifactDownloader,
26    this.artifactStager,
27    this.artifactCleaner,
28    this.installer,
29  });
```

After:

```dart
final session = JenkinsArtifactSession(
  store: tokenStore,
  client: jenkinsClient,
  stager: artifactStagingService,
);

AppSokShell(
  jobLoader: session.loadJobs,
  buildLoader: session.loadBuilds,
  artifactDownloader: session.downloadArtifact,
  artifactStager: session.stageApk,
  artifactCleaner: session.cleanup,
);
```

Service responsibilities:

- read validated `JenkinsSessionConfig` from `TokenStore`.
- call `JenkinsClient.fetchCurrentUser` during restore/check.
- call `JenkinsClient.fetchJobs`, `fetchRecentBuilds`, `downloadArtifactStream` with stored credentials.
- build artifact URL as `build.url` + `artifact/<relativePath>` using URI path joining, not string concat that drops path segments.
- expose `ArtifactStagingService.stageApk` and `cleanup`.

수정 파일 및 체크리스트:

- [ ] `lib/src/services/jenkins_artifact_session.dart`: new service wrapper 추가.
- [ ] `lib/src/services/jenkins_artifact_session.dart`: artifact URL join helper는 encoded path segment를 보존한다.
- [ ] `test/jenkins_artifact_session_test.dart`: restore success/failure, job/build delegation, artifact URL 조립 테스트 추가.

테스트 작성:

- 추가: `test/jenkins_artifact_session_test.dart`
- test names:
  - `restore validates stored session with current user api`
  - `loadJobs delegates to JenkinsClient with stored base url and credentials`
  - `downloadArtifact builds Jenkins artifact URL from build url and relative path`
  - `returns unavailable state when session config is missing`

중간 검증:

```bash
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com "zsh -lc 'cd \"\$HOME/docker/services/code-server/data/volume/workspace/appsok\" && flutter test test/jenkins_artifact_session_test.dart'"
```

기대 결과: 신규 service tests 통과.

### [API-2] App root production wiring

문제: [app.dart](/config/workspace/appsok/lib/src/app.dart:15)는 `home: const AppSokShell()`만 사용한다. 따라서 production 앱은 [builds_page.dart](/config/workspace/appsok/lib/src/features/builds/builds_page.dart:135)의 `jobLoader == null` 분기로 들어가고, artifact-browser 기능이 실제 앱 경로에서 도달 불가능하다.

해결 방법:

Before:

```dart
11    return MaterialApp(
12      debugShowCheckedModeBanner: false,
13      title: 'AppSok',
14      theme: AppTheme.light(),
15      home: const AppSokShell(),
16    );
```

After:

```dart
return MaterialApp(
  debugShowCheckedModeBanner: false,
  title: 'AppSok',
  theme: AppTheme.light(),
  home: AppSokShell(
    jobLoader: sessionReady ? _session.loadJobs : null,
    buildLoader: sessionReady ? _session.loadBuilds : null,
    artifactDownloader: sessionReady ? _session.downloadArtifact : null,
    artifactStager: _session.stageApk,
    artifactCleaner: _session.cleanup,
  ),
);
```

`AppSokApp`는 testability를 위해 optional dependencies를 받을 수 있게 한다. production default는 real `TokenStore`, `JenkinsClient`, `ArtifactStagingService`를 생성한다. app-shell rule에 맞춰 `AppSokShell` 안에서 Jenkins API/Keychain을 직접 호출하지 않고 root/service wiring만 수행한다.

수정 파일 및 체크리스트:

- [ ] `lib/src/app.dart`: `AppSokApp`를 stateful 또는 FutureBuilder 기반으로 변경하고 service dependency injection 추가.
- [ ] `lib/src/app.dart`: restore loading 중에도 shell layout이 크게 흔들리지 않도록 bounded loading/connection-required 상태 처리.
- [ ] `lib/src/features/app_shell.dart`: 필요 시 stager/cleaner props 전달만 유지하고 Jenkins API 직접 호출 금지.
- [ ] `test/widget_test.dart`: 기본 `AppSokApp` 경로 with stored session test 추가.

테스트 작성:

- 수정: `test/widget_test.dart`
- test names:
  - `AppSokApp wires stored Jenkins session into builds page`
  - `AppSokApp keeps Jenkins connection required state when no stored session exists`
  - `AppSokApp does not expose token text on auth failure`

중간 검증:

```bash
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com "zsh -lc 'cd \"\$HOME/docker/services/code-server/data/volume/workspace/appsok\" && flutter test test/widget_test.dart'"
```

기대 결과: 기존 shell tests와 신규 app root wiring tests 모두 통과.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/src/services/jenkins_artifact_session.dart` | API-1 |
| `lib/src/app.dart` | API-2 |
| `lib/src/features/app_shell.dart` | API-2 |
| `test/jenkins_artifact_session_test.dart` | API-1 |
| `test/widget_test.dart` | API-2 |

## 최종 검증

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

기대 결과: 출력 없음, exit code 0.

```bash
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com "zsh -lc 'cd \"\$HOME/docker/services/code-server/data/volume/workspace/appsok\" && flutter analyze && flutter test'"
```

기대 결과: `No issues found!`와 `All tests passed!`.

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