<!-- task=04+03_a2a_agent_surface plan=1 tag=A2A-review-fix -->

# Code Review Reference - A2A Review Fix

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Do not modify or check the `코드리뷰 전용 체크리스트`; it is owned by the review agent only.

## 개요

date=2026-05-20
task=04+03_a2a_agent_surface, plan=1, tag=A2A-review-fix

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

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G08.md` -> `code_review_cloud_G08_1.log`, `PLAN-cloud-G08.md` -> `plan_cloud_G08_1.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 task 디렉터리를 `agent-task/archive/YYYY/MM/04+03_a2a_agent_surface/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [FIX-1] A2A non-blocking 응답 snapshot 안정화 | [x] |
| [FIX-2] `configuration.blocking` 기본값 보정 | [x] |
| [FIX-3] config 예시와 cancel 전송 테스트 보강 | [x] |
| [FIX-4] 대상/전체/race/bin smoke 검증 재실행 | [x] |

## 구현 체크리스트

- [x] [FIX-1] A2A non-blocking 응답이 store 내부 포인터를 노출하지 않고 안정적인 snapshot을 반환하도록 수정한다.
- [x] [FIX-2] `configuration.blocking` omitted 값과 explicit false를 구분해 기본값을 계획대로 `true`로 맞춘다.
- [x] [FIX-3] A2A config 예시와 cancel 전송 테스트를 보강한다.
- [x] [FIX-4] 대상 테스트, 전체 테스트, A2A race 테스트, bin smoke 검증을 재실행한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_1.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G08_1.log`로 아카이브한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 `agent-task/04+03_a2a_agent_surface/`를 `agent-task/archive/YYYY/MM/04+03_a2a_agent_surface/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] WARN/FAIL이면 다음 active `PLAN-cloud-G08.md`와 `CODE_REVIEW-cloud-G08.md`를 작성하고 `complete.log`를 작성하지 않는다.

## 계획 대비 변경 사항

### FIX-1: non-blocking Task snapshot 분리

**변경 파일:** `apps/edge/internal/input/a2a/task_store.go`, `apps/edge/internal/input/a2a/server.go`

- `TaskStore.create`를 `string` 반환으로 변경해 내부 `*Task` 포인터를 외부에 노출하지 않는다.
- `TaskStore.get`에 `snapshotTask` 헬퍼를 추가해 `Artifacts.Parts`, `History.Parts`, `Status.Message`까지 완전한 deep copy를 수행한다.
- `server.go`의 non-blocking 경로에서 `s.tasks.get(taskID)` snapshot을 응답에 사용하도록 수정한다. background goroutine은 내부 포인터를 단독 소유한다.

### FIX-2: blocking 기본값 보정

**변경 파일:** `apps/edge/internal/input/a2a/types.go`, `apps/edge/internal/input/a2a/server.go`, `apps/edge/internal/input/a2a/server_test.go`, `apps/edge/internal/input/a2a/task_store_test.go`

- `SendConfig.Blocking`을 `bool`에서 `*bool`로 변경해 JSON field presence를 구분한다.
- blocking 기본값 로직: `params.Configuration == nil || params.Configuration.Blocking == nil || *params.Configuration.Blocking`
- 4가지 케이스(no config, empty config, explicit true, explicit false)를 `TestBlockingDefault`로 명시한다.
- 기존 테스트의 `Blocking: true/false`를 `Blocking: boolPtr(true/false)`로 수정한다.

### FIX-3: config 예시와 cancel 전송 테스트 보강

**변경 파일:** `configs/edge.yaml`, `apps/edge/internal/service/service.go`, `apps/edge/internal/service/service_test.go`

- `configs/edge.yaml`에 `a2a: enabled: false` 섹션 추가 (기본 포트/경로/adapter/session/timeout, bearer_token 빈 값).
- `service.go`에서 `BuildCancelRunRequest(req CancelRunRequest) *iop.CancelRequest` 함수를 추출한다. `CancelRun`이 이를 호출한다.
- `TestCancelRunRequestConstruction` → `TestBuildCancelRunRequest`로 교체: RunId, Adapter, Target, SessionId(normalized), CANCEL_ACTION_CANCEL_RUN을 모두 검증한다.

## 주요 설계 결정

### TaskStore 내부 포인터 소유권

non-blocking 경로에서 background goroutine이 내부 task 포인터를 소유하고 drain을 수행한다. 응답용 snapshot은 goroutine 시작 직후 `s.tasks.get(taskID)`로 얻는다. 이때 task는 아직 `working` 상태이거나 goroutine이 이미 drain을 완료했을 수 있지만, 어느 경우든 race-free이다. 내부 포인터를 공유하는 대신 snapshot을 통해 "goroutine이 소유한다"는 불변식을 유지한다.

### `*bool` field presence 패턴

JSON `{}` 디코드 시 `bool` 필드는 zero value(`false`)로 채워진다. `*bool`은 nil(omitted)과 explicit false를 구분한다. `SendConfig` 구조체는 이 패턴을 적용해 nil → true(default), &false → false를 표현한다.

### BuildCancelRunRequest 추출

`BuildRunRequest`와 동일한 패턴으로 proto 생성 로직을 service 외부에서 직접 테스트할 수 있도록 추출한다. `NormalizeSessionID` 호출이 포함돼 empty session을 DefaultSessionID로 맞추는 동작을 검증한다. 새 production abstraction을 추가하지 않고 기존 패턴을 재사용한다.

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

- non-blocking `message/send` 응답이 내부 mutable task 포인터를 공유하지 않는지 확인한다.
- `configuration` 없음, 빈 객체, explicit true, explicit false의 blocking 동작이 계획과 테스트에서 일치하는지 확인한다.
- `TaskStore.get` snapshot이 필요한 범위에서 deep copy인지 확인한다.
- `Service.CancelRun` 테스트가 실제 `iop.CancelRequest` payload를 검증하는지 확인한다.
- `configs/edge.yaml` 예시가 config defaults와 충돌하지 않는지 확인한다.
- 검증 결과가 현재 소스와 재현되는지 확인한다.

## 검증 결과

```
$ go build ./...
(no output — clean build)

$ go test -count=1 ./packages/config ./apps/edge/internal/input/... ./apps/edge/internal/bootstrap ./apps/edge/internal/service
ok  	iop/packages/config	0.009s
ok  	iop/apps/edge/internal/input	0.004s
ok  	iop/apps/edge/internal/input/a2a	0.005s
ok  	iop/apps/edge/internal/bootstrap	0.005s
ok  	iop/apps/edge/internal/service	0.004s

$ go test -race -count=1 ./apps/edge/internal/input/a2a
ok  	iop/apps/edge/internal/input/a2a	1.017s

$ go test -count=1 ./...
ok  	iop/apps/control-plane/cmd/control-plane	0.007s
ok  	iop/apps/edge/cmd/edge	0.009s
ok  	iop/apps/edge/internal/bootstrap	0.006s
ok  	iop/apps/edge/internal/events	0.003s
ok  	iop/apps/edge/internal/input	0.006s
ok  	iop/apps/edge/internal/input/a2a	0.008s
ok  	iop/apps/edge/internal/node	0.005s
ok  	iop/apps/edge/internal/openai	1.510s
ok  	iop/apps/edge/internal/opsconsole	0.007s
ok  	iop/apps/edge/internal/service	0.004s
ok  	iop/apps/edge/internal/transport	0.011s
ok  	iop/apps/node/cmd/node	0.009s
ok  	iop/apps/node/internal/adapters	0.005s
ok  	iop/apps/node/internal/adapters/cli	41.244s
ok  	iop/apps/node/internal/adapters/cli/status	32.282s
ok  	iop/apps/node/internal/adapters/ollama	0.006s
ok  	iop/apps/node/internal/adapters/vllm	0.006s
ok  	iop/apps/node/internal/bootstrap	0.162s
ok  	iop/apps/node/internal/node	0.009s
ok  	iop/apps/node/internal/router	0.004s
ok  	iop/apps/node/internal/store	0.021s
ok  	iop/apps/node/internal/transport	5.044s
ok  	iop/packages/config	0.010s
ok  	iop/packages/hostsetup	0.005s

$ scripts/e2e-smoke.sh
[e2e] NOTE: auxiliary smoke only; completion requires bin/edge.sh + bin/node.sh user-flow verification.
[e2e] Auxiliary smoke test PASSED.
[e2e] Completion still requires bin/edge.sh + bin/node.sh user-flow verification.
```

`scripts/e2e-smoke.sh`는 보조 smoke이다. 직접 bin shell 사용자 흐름(bin/edge.sh + bin/node.sh)과 full-cycle/real CLI profile은 수행하지 않았다. A2A HTTP 엔드포인트에 대한 end-to-end 검증은 실제 node 연결 환경이 필요해 이 환경에서는 수행할 수 없다.

## 코드리뷰 결과

### 판정

PASS

### 차원별 평가

| 차원 | 평가 | 근거 |
|------|------|------|
| 기능 정확성 | PASS | `configuration` 없음/빈 객체/explicit true/explicit false의 blocking 기본값이 `*bool` presence 패턴과 `TestBlockingDefault`로 일치한다. |
| 동시성/상태 관리 | PASS | `TaskStore.create`가 내부 포인터를 반환하지 않고, `TaskStore.get`이 `Status.Message`, `Artifacts.Parts`, `History.Parts`까지 snapshot을 반환한다. non-blocking 응답도 snapshot만 encoding한다. |
| 테스트 신뢰성 | PASS | 대상 테스트, A2A race, 전체 테스트, 보조 smoke, 직접 bin user-flow, A2A HTTP 실제 edge/node flow를 재실행해 모두 통과했다. |
| 설정/문서 | PASS | `configs/edge.yaml`의 disabled `a2a` 예시와 `packages/config` default/override 테스트가 일치한다. |

### Required

- 없음.

### Suggested

- 없음.

### Nit

- 없음.

### 확인한 핵심 지점

- `apps/edge/internal/input/a2a/server.go:165`의 blocking default 로직이 omitted 값을 `true`로 처리한다.
- `apps/edge/internal/input/a2a/server.go:191` 이후 non-blocking 응답이 `s.tasks.get(taskID)` snapshot을 사용한다.
- `apps/edge/internal/input/a2a/task_store.go:22`와 `apps/edge/internal/input/a2a/task_store.go:35`의 snapshot path가 nested parts를 복사한다.
- `apps/edge/internal/service/service.go:200`의 `BuildCancelRunRequest`가 `RunId`, `Adapter`, `Target`, normalized `SessionId`, `CANCEL_ACTION_CANCEL_RUN`을 구성하고 `CancelRun`이 이를 전송한다.

### 리뷰 검증 재실행

```bash
$ go build ./...
(no output — clean build)

$ go test -count=1 ./packages/config ./apps/edge/internal/input/... ./apps/edge/internal/bootstrap ./apps/edge/internal/service
ok  	iop/packages/config	0.008s
ok  	iop/apps/edge/internal/input	0.004s
ok  	iop/apps/edge/internal/input/a2a	0.005s
ok  	iop/apps/edge/internal/bootstrap	0.005s
ok  	iop/apps/edge/internal/service	0.004s

$ go test -race -count=1 ./apps/edge/internal/input/a2a
ok  	iop/apps/edge/internal/input/a2a	1.025s

$ go test -count=1 ./...
ok  	iop/apps/control-plane/cmd/control-plane	0.006s
ok  	iop/apps/edge/cmd/edge	0.020s
ok  	iop/apps/edge/internal/bootstrap	0.006s
ok  	iop/apps/edge/internal/events	0.006s
ok  	iop/apps/edge/internal/input	0.006s
ok  	iop/apps/edge/internal/input/a2a	0.016s
ok  	iop/apps/edge/internal/node	0.006s
ok  	iop/apps/edge/internal/openai	1.514s
ok  	iop/apps/edge/internal/opsconsole	0.006s
ok  	iop/apps/edge/internal/service	0.010s
ok  	iop/apps/edge/internal/transport	0.007s
ok  	iop/apps/node/internal/adapters/cli	41.460s
ok  	iop/apps/node/internal/adapters/cli/status	32.221s
ok  	iop/apps/node/internal/transport	5.049s
ok  	iop/packages/config	0.011s
ok  	iop/packages/hostsetup	0.006s
```

추가 검증:

- `scripts/e2e-smoke.sh` - PASS; auxiliary smoke 기준으로 node registration, `/nodes`, `/capabilities`, `/transport`, message 2회, background run, `/sessions`, `/terminate-session` 확인.
- 직접 `bin/edge.sh` + `bin/node.sh` 사용자 흐름 - PASS; 임시 fake-cli config로 node registration, `/nodes`, `/capabilities`, `/transport`, 같은 session 메시지 2회, node local payload와 edge rendered payload, `/sessions`, `/terminate-session` 확인.
- A2A HTTP 실제 edge/node flow - PASS; 임시 fake-cli node 연결 후 `message/send` 기본 blocking 응답 completed artifact 확인, explicit `blocking:false` 응답 task id 확인, `tasks/get` completed artifact 확인.

잔여 위험:

- 실제 외부 CLI profile(`claude`, `gemini`, `codex`, `opencode`) full-cycle은 이번 리뷰에서 수행하지 않았다. 이 변경은 fake-cli 기반 edge/node/A2A 흐름으로 검증했으며 외부 계정/CLI 상태 의존 검증은 별도 real-profile 검증 범위로 남는다.

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave the review-agent-only checklist unchanged.

## Section Ownership

| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these archive instructions |
| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` -> `[x]` only |
| 구현 체크리스트 | Implementing agent | Check `[ ]` -> `[x]` only |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus areas |
| 검증 결과 | Implementing agent | Fill command output only |
