<!-- task=m-architecture-refactor-foundation/02_edge_service_split plan=0 tag=REFACTOR -->

# Edge Service Split Plan

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

`CODE_REVIEW-cloud-G05.md`의 구현 에이전트 소유 섹션 작성까지가 구현의 일부다. 코드 변경 후 검증을 실행하고 실제 변경 내용, 검증 출력, 계획 대비 변경 사항을 채운 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 사용자 결정, 사용자 소유 외부 환경, 범위 충돌 때문에 진행할 수 없으면 review stub의 `사용자 리뷰 요청`에 근거와 재개 조건을 적고 중단한다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 최종 판정, log archive, `complete.log` 작성은 code-review 전용이다.

## 배경

`apps/edge/internal/service/service.go`는 node snapshot/status provider, run dispatch, cancel/session commands, node command transport, Ollama passthrough, Control Plane command execution을 한 파일에서 처리한다. Control Plane connector와 console/openai/A2A 입력 표면이 service를 공유하므로, service 내부 책임 경계가 흐리면 후속 terminal bridge와 운영 command 확장이 서로 얽힌다. 이 작업은 service package API를 보존하면서 파일/헬퍼 경계를 나누고, `health.check`의 임시 sleep 성공을 실제 Edge-owned status 요약 경계로 대체한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active `CODE_REVIEW-cloud-G05.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 직접 사용자 프롬프트를 만들지 않으며, code-review가 요청의 타당성을 판단하고 필요할 때만 `USER_REVIEW.md`를 작성한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/architecture-refactor-foundation.md`
- Task ids:
  - `edge-service-split`: `apps/edge/internal/service`를 run dispatch, node command, control command, capability/status provider 책임으로 분리하고 health simulation 같은 임시 command 구현을 실제 상태 조회 경계와 분리한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/architecture-refactor-foundation.md`
- `apps/edge/internal/service/service.go`
- `apps/edge/internal/service/service_test.go`

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`를 읽었다.
- matching profile: `agent-test/local/edge-smoke.md`.
- 적용 명령: 변경한 edge 패키지 또는 `go test ./apps/edge/...`.
- service는 registry/command/status boundary라 focused `go test ./apps/edge/internal/service` 뒤에 connector/console/input call-site 포함 `go test ./apps/edge/...`를 실행한다.

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

- `BuildRunRequest`, `ResolveNode`, `ListNodeSnapshots`, capabilities/domain agents, `ExecuteCommand`, node command success/error는 기존 `service_test.go`가 다룬다.
- `health.check`는 현재 event sequence/status 정도만 검증하며 실제 status summary 내용은 약하다. 구현 시 `health.check`가 registry/capability snapshot을 사용한다는 테스트를 추가한다.

### 심볼 참조

- `SubmitRun`, `RunHandle`, `RunDispatch`, `RunStream`은 openai, A2A, opsconsole, tests에서 참조된다.
- `ListNodeSnapshots`, `GetCapabilities`, `ExecuteCommand`는 controlplane connector status provider interface에서 참조된다.
- `NodeCommandView`, `UsageStatus`, `Capabilities`, `SessionList`, `TransportStatus`는 opsconsole status rendering에서 참조된다.
- public service method/type names should not be renamed in this task.

### 분할 판단

- shared task group: `agent-task/m-architecture-refactor-foundation/`.
- sibling subtasks: `01_edge_cmd_split` independent, `02_edge_service_split` independent, `03+02_openai_surface_split` waits for this task.
- 이 plan은 service package 내부의 responsibility split이며, openai split은 service API/stream semantics와 충돌할 수 있어 후속 dependent task로 둔다.

### 범위 결정 근거

- `apps/edge/internal/openai/**`와 `apps/edge/cmd/edge/**`는 이 plan에서 수정하지 않는다.
- service package import path와 public method/type names는 유지한다.
- Control Plane proto 계약, node transport proto, registry implementation은 변경하지 않는다.

### 빌드 등급

- `cloud-G05`: control command/status provider 의미를 정리하고 여러 입력 표면이 의존하는 service 경계를 다루므로 architecture judgment와 regression review가 중요하다.

## 구현 체크리스트

- [ ] `apps/edge/internal/service`를 같은 package 안에서 `service.go`, `status_provider.go`, `run_dispatch.go`, `node_command.go`, `control_command.go` 같은 책임별 파일로 나눈다.
- [ ] `ExecuteCommand("health.check")`의 `time.Sleep` 기반 임시 성공을 제거하고 `ListNodeSnapshots`/`GetCapabilities` 기반 `HealthStatus` 또는 equivalent private boundary를 사용하게 한다.
- [ ] 기존 public service API를 유지하고 openai/A2A/opsconsole/controlplane call-site 변경이 없거나 mechanical import-only 수준인지 확인한다.
- [ ] `go test ./apps/edge/internal/service`와 `go test ./apps/edge/...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR-1] Split Service Responsibilities

#### 문제

- [service.go](/config/workspace/iop/apps/edge/internal/service/service.go:24)는 service dependencies를 정의한 뒤 같은 파일에서 status provider, run dispatch, node commands, control commands까지 이어진다.
- [service.go](/config/workspace/iop/apps/edge/internal/service/service.go:609)는 `ExecuteCommand`가 health simulation, agent status, node command routing을 한 switch에 모으고, [service.go](/config/workspace/iop/apps/edge/internal/service/service.go:619)는 `time.Sleep`으로 임시 health check 성공을 만든다.

#### 해결 방법

Before:

```go
// apps/edge/internal/service/service.go:609
func (s *Service) ExecuteCommand(ctx context.Context, req *iop.EdgeCommandRequest, onEvent func(*iop.EdgeCommandEvent)) (*iop.EdgeCommandResponse, error) {
    switch req.Operation {
    case "health.check":
        onEvent(started)
        time.Sleep(10 * time.Millisecond) // simulation
        onEvent(completed)
        return &iop.EdgeCommandResponse{Status: "completed", Summary: "Edge health check OK"}, nil
    ...
    }
}
```

After:

```go
// apps/edge/internal/service/control_command.go
func (s *Service) ExecuteCommand(ctx context.Context, req *iop.EdgeCommandRequest, onEvent func(*iop.EdgeCommandEvent)) (*iop.EdgeCommandResponse, error) {
    return s.controlCommands().Execute(ctx, req, onEvent)
}

// apps/edge/internal/service/status_provider.go
func (s *Service) HealthStatus() HealthStatusView {
    return HealthStatusView{Nodes: s.ListNodeSnapshots(), Capabilities: s.GetCapabilities()}
}
```

- Keep the receiver methods on `*Service` for compatibility.
- Move private helpers near their responsibility; keep shared helpers such as `normalizeTimeoutSec` in a small common file.
- Add a service test that constructs registry state and asserts `health.check` summary/events are derived from service status, not a fixed fake delay.

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

- [ ] `apps/edge/internal/service/service.go`
- [ ] `apps/edge/internal/service/status_provider.go`
- [ ] `apps/edge/internal/service/run_dispatch.go`
- [ ] `apps/edge/internal/service/node_command.go`
- [ ] `apps/edge/internal/service/control_command.go`
- [ ] `apps/edge/internal/service/service_test.go` or split `*_test.go` files

#### 테스트 작성

- Add or update service tests for `health.check` status summary.
- Existing node command and run dispatch tests must remain green without changing external call sites.

#### 중간 검증

```bash
go test ./apps/edge/internal/service
```

Expected: service tests pass and cover the new status-backed health command.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/internal/service/service.go` | REFACTOR-1 |
| `apps/edge/internal/service/status_provider.go` | REFACTOR-1 |
| `apps/edge/internal/service/run_dispatch.go` | REFACTOR-1 |
| `apps/edge/internal/service/node_command.go` | REFACTOR-1 |
| `apps/edge/internal/service/control_command.go` | REFACTOR-1 |
| `apps/edge/internal/service/*_test.go` | REFACTOR-1 |

## 최종 검증

```bash
go test ./apps/edge/...
```

Expected: all edge package tests pass. Go test cache output is acceptable for unchanged packages; use `-count=1` if service/control command behavior appears stale.

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