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

# Code Review Reference - SSM

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**

## 개요

date=2026-05-17
task=03_service_source_metadata, plan=0, tag=SSM

## 이 파일을 읽는 리뷰 에이전트에게

구현과 검증 출력을 대조하고 PASS 시 plan/review를 log로 아카이브하세요.

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|---|---|
| [SSM-1] service의 source hardcode 제거 | [x] |
| [SSM-2] ops console caller가 source metadata 명시 | [x] |

## 계획 대비 변경 사항

- 계획과 동일하게 진행: service.go의 BuildRunRequest에서 hardcoded `"source": "edge-console"` 제거, console.go의 SendRun에서 `Metadata: {"source": "edge-ops-console"}` 명시

## 주요 설계 결정

- `BuildRunRequest`는 이제 caller-neutral: `req.Metadata`를 빈 map으로 복사만 하고 source를 주입하지 않음
- console caller(`edge-ops-console`)만이 source를 명시하며, 다른 caller(예: HTTP API)도 동일한 `SubmitRunRequest`를 쓰되 자신의 source를 metadata에 담을 수 있음

## 리뷰어를 위한 체크포인트

- Service가 `edge-console`을 기본 주입하지 않는가. → PASS: service.go에서 `"source": "edge-console"` 제거됨
- Caller metadata가 손실되지 않는가. → PASS: `TestBuildRunRequestCopiesMetadata`에서 `x-custom: value`와 `source: edge-ops-console`이 모두 손실 없이 전달됨
- Console caller가 source를 명시하는가. → PASS: console.go SendRun에서 `"source": "edge-ops-console"` 포함

## 검증 결과

```bash
$ go test -count=1 ./apps/edge/internal/service ./apps/edge/internal/opsconsole ./apps/edge/cmd/edge
ok  	iop/apps/edge/internal/service	0.003s
ok  	iop/apps/edge/internal/opsconsole	0.004s
?   	iop/apps/edge/cmd/edge	[no test files]

$ go test -count=1 ./...
ok  	iop/apps/edge/internal/bootstrap	0.005s
ok  	iop/apps/edge/internal/events	0.004s
ok  	iop/apps/edge/internal/node	0.013s
ok  	iop/apps/edge/internal/opsconsole	0.009s
ok  	iop/apps/edge/internal/service	0.007s
ok  	iop/apps/edge/internal/transport	0.015s
ok  	iop/apps/node/internal/adapters	0.012s
ok  	iop/apps/node/internal/adapters/cli	87.257s
ok  	iop/apps/node/internal/adapters/cli/status	30.721s
ok  	iop/apps/node/internal/bootstrap	0.160s
ok  	iop/apps/node/internal/node	0.009s
ok  	iop/apps/node/internal/router	0.004s
ok  	iop/apps/node/internal/store	0.041s
ok  	iop/apps/node/internal/transport	0.006s
ok  	iop/packages/config	0.008s
```

모든 테스트 통과 - 0FAIL

## 코드리뷰 결과

종합 판정: PASS

차원별 평가

- Correctness: Pass — `BuildRunRequest`가 더 이상 `source`를 기본 주입하지 않고, caller metadata를 그대로 복사한다.
- Completeness: Pass — SSM-1/SSM-2 계획 항목이 모두 반영됐다.
- Test coverage: Pass — service metadata no-inject/copy 테스트가 있고, console caller source는 실제 `SendRun` 생성부와 smoke로 확인했다.
- API contract: Pass — `SubmitRunRequest.Metadata` 계약은 caller-owned로 유지되며 기존 `RunRequest.Metadata` 전달 형태는 유지된다.
- Code quality: Pass — 변경 범위에 디버그 출력, TODO, 불필요한 protocol/schema 변경이 없다.
- Plan deviation: Pass — opsconsole 패키지 이동 상태에 맞춰 `apps/edge/internal/opsconsole/console.go`에서 source를 명시한 점은 계획의 허용 범위다.
- Verification trust: Pass — 리뷰 중 대상 패키지 테스트, 전체 테스트, mock `bin/edge.sh`/`bin/node.sh` smoke를 재실행해 통과를 확인했다.

발견된 문제: 없음

검증 재확인

- `go test -count=1 ./apps/edge/internal/service ./apps/edge/internal/opsconsole ./apps/edge/cmd/edge` — PASS
- `go test -count=1 ./...` — PASS
- mock E2E smoke (`bin/edge.sh` + `bin/node.sh`, 임시 config, mock adapter) — PASS: node register, `/nodes`, foreground message x2, session 변경 후 run, background run, `/terminate-session`, `/status` unsupported error, node disconnect lifecycle 확인

다음 단계: PASS — plan/review를 log로 아카이브하고 `complete.log`를 작성한다.
