<!-- task=m-multi-edge-operations/02+01_history_agent_client plan=0 tag=API -->

# Plan - History Agent State Client View

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

구현 완료는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션까지 실제 구현 내용과 검증 출력으로 채운 뒤에만 성립한다. 검증 명령을 실행하고 실제 stdout/stderr를 review stub에 붙인다. active 파일은 그대로 두고 리뷰 준비 상태를 보고하며, `complete.log` 작성, `.log` rename, archive 이동은 code-review 스킬 전용이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청`에 정확한 근거를 남기고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

Runtime panel은 domain agent summary와 operations history를 운영자가 보는 1차 화면이다. 현재 test fixture는 모든 Edge에 같은 domain agents와 operations를 돌려주므로, Edge 선택 변경 시 history/agent state가 실제로 분리 표시되는지 검증하지 못한다. 이 작업은 backend 계약 subtask가 통과한 뒤 Flutter 화면과 repository fixture를 Edge별로 분리해 `history-agent-state` 완료 근거를 만든다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 해당 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 기준이며, 실제 `USER_REVIEW.md` 작성 여부 판단은 code-review가 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/control-plane-portal-ops/milestones/multi-edge-operations.md`
- Task ids:
  - `history-agent-state`: Edge별 실행/명령 결과와 domain agent capability/status/command summary가 운영 화면에서 구분된다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/control-plane-portal-ops/PHASE.md`
- `agent-roadmap/phase/control-plane-portal-ops/milestones/multi-edge-operations.md`
- `agent-test/local/rules.md`
- `agent-test/local/client-smoke.md`
- `agent-test/local/control-plane-smoke.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-ops/rules/project/domain/client/rules.md`
- `agent-ops/rules/project/domain/control-plane/rules.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `apps/client/lib/control_plane_status_client.dart`
- `apps/client/lib/control_plane_status_widgets.dart`
- `apps/client/lib/main.dart`
- `apps/client/test/widget_test.dart`
- `apps/control-plane/cmd/control-plane/main.go`
- `apps/control-plane/cmd/control-plane/main_test.go`
- `apps/control-plane/internal/wire/edge_registry.go`
- `apps/control-plane/internal/wire/edge_server.go`
- `apps/edge/internal/service/service.go`
- `apps/edge/internal/service/service_test.go`
- `proto/iop/control.proto`

### 테스트 환경 규칙

- test_env: `local`.
- `agent-test/local/rules.md` present/read. Matching profiles read: `client-smoke`, `control-plane-smoke`, `edge-smoke`, `platform-common-smoke`.
- 적용 명령: client 변경은 최소 `cd apps/client && flutter test`; analyzer 영향이 있으면 `cd apps/client && flutter analyze --no-fatal-infos`.
- 현재 작업 컨테이너에서 `cd apps/client && flutter test test/widget_test.dart`가 이미 성공했다. 원격 runtime evidence는 web/dev-server 또는 Docker compose 변경이 있을 때만 필요하다.
- `<확인 필요>` 값 없음.

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

- `apps/client/test/widget_test.dart:199`의 `fetchFleetStatus`는 모든 Edge에 동일한 domain agents를 반환한다.
- `apps/client/test/widget_test.dart:247`의 `fetchEdgeOperations`는 어떤 Edge ID에도 같은 operation을 반환한다.
- `apps/client/test/widget_test.dart:553`의 Runtime panel test는 기본 선택 Edge 하나만 검증한다. Edge 선택 변경 후 domain agent summary, active command, operations history가 바뀌는지 확인하지 않는다.
- `apps/client/lib/control_plane_status_widgets.dart:856`의 `_fetchOperations`는 새 Edge fetch 시작 시 기존 `_operations`를 유지한다. 느린 요청이나 선택 변경 중 stale operation이 보일 수 있는 위험이 있다.

### 심볼 참조

- none. Rename/remove 없음.

### 분할 판단

- split decision policy 적용 완료.
- shared task group: `agent-task/m-multi-edge-operations`.
- predecessor: `01_history_agent_contract` is required by directory name `02+01_history_agent_client`; active predecessor exists but `complete.log` is missing. Implementation must wait until `agent-task/m-multi-edge-operations/01_history_agent_contract/complete.log` or archived equivalent exists.
- 이 subtask는 backend contract 고정 후 client UI/state/test를 다룬다. Backend 수정은 predecessor 범위로 남긴다.

### 범위 결정 근거

- 포함: `apps/client/lib/control_plane_status_widgets.dart`, `apps/client/test/widget_test.dart`.
- 포함 가능: repository model parsing gap이 있으면 `apps/client/lib/control_plane_status_client.dart`; selected-edge state propagation gap이 있으면 `apps/client/lib/main.dart`.
- 제외: Go backend contract 수정, proto 변경, durable audit/history 저장, domain-agent artifact/log 상세 표시, NomadCode-specific shell UX.
- UI는 Control Plane HTTP 표면을 소비하며 Edge/Node TCP transport에 직접 붙지 않는다.

### 빌드 등급

- build lane/grade: `local-G06`. Flutter state/UI test가 핵심이고 predecessor contract에 의존하지만, bounded widget tests로 검증 가능하다.

## 구현 체크리스트

- [ ] `01_history_agent_contract` predecessor complete.log가 있는지 확인한다.
- [ ] Runtime panel이 Edge 선택 변경과 느린 fetch 중 stale operations/domain agent state를 섞지 않도록 보강한다.
- [ ] Fake repository가 Edge별로 서로 다른 domain agents, active command id, operations history, command target Edge를 반환/기록하도록 만든다.
- [ ] widget test에서 Runtime panel의 Edge 선택 변경 후 domain agents와 operations history가 Edge별로 구분되는지 검증한다.
- [ ] `cd apps/client && flutter test test/widget_test.dart`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

이 subtask 디렉터리명은 `02+01_history_agent_client`다. predecessor index `01`은 `agent-task/m-multi-edge-operations/01_history_agent_contract`이며, 구현 시작 전 active 또는 archived `complete.log`가 필요하다. 현재 작성 시점에는 predecessor complete.log가 없다.

### [API-1] Runtime Panel Clears Stale Edge Operations

문제: `apps/client/lib/control_plane_status_widgets.dart:849`는 selected Edge가 바뀌면 `_fetchOperations()`를 호출하지만, `control_plane_status_widgets.dart:856`은 fetch 시작 시 기존 `_operations`를 지우지 않는다. `_buildOperationsHistory`는 `control_plane_status_widgets.dart:1379`에서 `_operations`가 nil일 때만 loading을 보여주므로, 새 Edge 로딩 중 이전 Edge operations가 보일 수 있다.

해결 방법: `_RuntimePanelState`에 `_operationsEdgeId` 같은 owner field를 두거나, `_fetchOperations` 시작 시 `_operations = null`로 초기화한다. async completion 시 `mounted && widget.selectedEdgeId == edgeId`를 확인한 뒤에만 `_operations`를 set한다. 이 변경은 selected Edge가 빠르게 바뀌어도 stale result가 화면에 남지 않게 한다.

수정 파일 및 체크리스트:

- [ ] `apps/client/lib/control_plane_status_widgets.dart`: operations owner Edge ID tracking 또는 fetch-start clear 추가.
- [ ] `apps/client/lib/control_plane_status_widgets.dart`: async response guard 추가.

테스트 작성: widget test에서 Edge switch 후 이전 Edge operation 텍스트가 사라지는 assertion으로 검증한다.

중간 검증:

```bash
cd apps/client && flutter test test/widget_test.dart -r expanded --plain-name 'Client App opens Operations & Domain Agents panel and verifies agents, operations history, and command triggering'
```

### [API-2] Edge-Specific Runtime Fixture And Assertions

문제: `apps/client/test/widget_test.dart:199`은 fleet status fixture를 `mockEdges.map`으로 만들며 모든 Edge에 같은 domain agents를 붙인다. `apps/client/test/widget_test.dart:247`은 edgeId를 받지만 operation 내용은 항상 `oto-agent.sync`/`Synced 5 models`다. 따라서 `history-agent-state`의 Edge별 구분 요구를 테스트가 보장하지 않는다.

해결 방법: `FakeControlPlaneStatusRepository`에 Edge별 fixture helpers를 추가한다. 예: edge-a는 `oto-agent READY`, `build-deploy READY`, `cmd-alpha-active`, `oto-agent.sync`, `Synced 5 models`; edge-b는 `oto-agent BUSY` 또는 `ERROR`, `cmd-beta-active`, `build-deploy.deploy`, `Deploy queued for edge beta`. Runtime panel test에서 dropdown으로 edge-b를 선택하고 alpha-only texts가 사라지고 beta-only texts가 나타나는지 검증한다. `sendEdgeCommand`에는 `lastCommandEdgeId`를 기록해 선택된 Edge로 command가 나가는지도 확인한다.

수정 파일 및 체크리스트:

- [ ] `apps/client/test/widget_test.dart`: Edge별 domain agent fixture 분리.
- [ ] `apps/client/test/widget_test.dart`: Edge별 operations fixture 분리.
- [ ] `apps/client/test/widget_test.dart`: Runtime panel Edge switch assertion 추가.
- [ ] `apps/client/test/widget_test.dart`: selected Edge command dispatch assertion 추가.

테스트 작성: 작성. 기존 Runtime panel widget test를 확장하거나 새 focused widget test를 추가한다.

중간 검증:

```bash
cd apps/client && flutter test test/widget_test.dart -r expanded --plain-name 'Client App opens Operations & Domain Agents panel and verifies agents, operations history, and command triggering'
```

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/client/lib/control_plane_status_widgets.dart` | API-1 |
| `apps/client/test/widget_test.dart` | API-1, API-2 |
| `apps/client/lib/control_plane_status_client.dart` | API-2 only if parsing/model gap appears |
| `apps/client/lib/main.dart` | API-1 only if selected-edge state propagation gap appears |

## 최종 검증

```bash
cd apps/client && flutter test test/widget_test.dart
```

기대 결과: all tests passed. Flutter widget test cache output is not applicable; record actual command output.

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