<!-- task=03_service_source_metadata plan=0 tag=SSM -->

# 03 Service Source Metadata Plan - SSM

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

**필수: 구현 마지막에는 반드시 `CODE_REVIEW-*-G??.md`의 모든 섹션을 실제 구현 내용과 명령 출력으로 채운다.**
이 task는 service의 caller-neutral metadata 정책만 다룬다.

## 배경

향후 HTTP/API와 ops console이 같은 edge service를 호출해야 한다. 그런데 현재 `BuildRunRequest`가 모든 request source를 `edge-console`로 강제한다.

## 분석 결과

### 읽은 파일

- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `apps/edge/internal/service/service.go`
- `apps/edge/internal/service/service_test.go`
- `apps/edge/cmd/edge/console.go`
- `apps/edge/cmd/edge/console_test.go`
- `packages/events/events.go`

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

- service가 source metadata를 주입하지 않는다는 테스트가 없다.
- caller metadata merge 우선순위 테스트가 부족하다.

### 심볼 참조

- `BuildRunRequest`: `service.go:244`, calls `service.go:84`, `service_test.go:12`, `console_test.go:17`.
- `SubmitRunRequest.Metadata`: `service.go:48`, merge `service.go:253`.

### 범위 결정 근거

- opsconsole package 이동은 `02_edge_opsconsole_package`에서 처리한다.
- HTTP API 구현은 제외한다.

### 빌드 등급

- Build `local-G04`: 좁은 service behavior change와 tests.
- Review `local-G04`: metadata regression 확인 중심.

### [SSM-1] service의 source hardcode 제거

#### 문제

```go
// apps/edge/internal/service/service.go:253
metadata := map[string]string{"source": "edge-console"}
```

#### 해결 방법

- `BuildRunRequest`는 `req.Metadata`를 nil-safe copy만 한다.
- metadata가 nil이면 빈 map 또는 nil 중 하나를 명시적으로 선택한다. Prefer: 빈 map.

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

- [x] `apps/edge/internal/service/service.go`
- [x] `apps/edge/internal/service/service_test.go`

#### 테스트 작성

- `TestBuildRunRequestDoesNotInjectConsoleSource`
- `TestBuildRunRequestCopiesMetadata`

#### 중간 검증

```bash
go test -count=1 ./apps/edge/internal/service
```

### [SSM-2] ops console caller가 source metadata 명시

#### 문제

source가 service에서 사라지면 console caller가 source를 명시해야 한다.

#### 해결 방법

- console/opsconsole `SubmitRunRequest` 생성 시 `Metadata: map[string]string{"source": "edge-ops-console"}`를 넣는다.
- 기존 `edge-console` 문자열 호환이 필요하면 문서화하고 그대로 유지한다.

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

- [x] `apps/edge/cmd/edge/console.go` 또는 `apps/edge/internal/opsconsole/console.go`
- [x] 관련 console test

#### 테스트 작성

- console submit helper test에서 metadata source 확인.

#### 중간 검증

```bash
go test -count=1 ./apps/edge/internal/service ./apps/edge/cmd/edge ./apps/edge/internal/opsconsole
```

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `apps/edge/internal/service/service.go` | SSM-1 |
| `apps/edge/internal/service/service_test.go` | SSM-1 |
| `apps/edge/cmd/edge/console.go` | SSM-2 |
| `apps/edge/internal/opsconsole/console.go` | SSM-2 |

## 최종 검증

```bash
go test -count=1 ./apps/edge/internal/service ./apps/edge/cmd/edge ./apps/edge/internal/opsconsole
go test -count=1 ./...
```

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