<!-- task=m-runtime-reconnect-config-refresh/04+02_config_push_contract plan=3 tag=REVIEW_REVIEW_REVIEW_NCPUSH -->

# Plan - REVIEW_REVIEW_REVIEW_NCPUSH

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증을 실행하고, 실제 구현 내용과 실제 stdout/stderr를 리뷰 stub에 채운 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 최종 archive, `complete.log`, roadmap 완료 처리는 code-review/runtime 책임이다. 선택된 Milestone의 `구현 잠금 > 결정 필요` 항목 때문에 구현이 막힐 때만 리뷰 stub의 `사용자 리뷰 요청` 섹션에 정확한 근거를 적고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하거나, `USER_REVIEW.md`/`complete.log`를 직접 만들지 않는다. 환경/secret/service 차단, 일반 범위 조정, 후속 에이전트가 닫을 수 있는 evidence 공백은 사용자 리뷰 요청이 아니다.

## 배경

이 follow-up은 `REVIEW_REVIEW_NCPUSH` 리뷰에서 발견된 no-change refresh push 계약 오류만 보완한다. 현재 구현은 `StatusApplied`인 apply refresh마다 node refresh push를 수행한다. 그런데 `StatusApplied`는 실제 변경이 있는 applied 결과뿐 아니라 no-change 결과도 포함한다. 연결된 Node는 non-nil `NodeConfigPayload`를 받으면 현재 phase에서 `restart_required`로 응답하므로, 동일 config apply가 node `restart_required`로 보고될 수 있다.

## 사용자 리뷰 요청 흐름

구현 중 사용자 리뷰 요청은 선택된 Milestone lock decision이 실제 구현을 막을 때만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 해당 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 템플릿을 따른다. 구현 에이전트는 직접 사용자에게 질문하지 않으며, code-review가 요청 타당성을 검증하고 필요할 때만 `USER_REVIEW.md`를 작성한다.

## Archive Evidence Snapshot

- Current archived plan: `agent-task/m-runtime-reconnect-config-refresh/04+02_config_push_contract/plan_cloud_G07_2.log`
- Current archived review: `agent-task/m-runtime-reconnect-config-refresh/04+02_config_push_contract/code_review_cloud_G07_2.log`
- Verdict: FAIL
- Required summary:
  - `apps/edge/internal/bootstrap/runtime.go:184` treats every `StatusApplied` result as a node-push trigger.
  - `configrefresh.Classify` returns `StatusApplied` even when there are zero changes (`apps/edge/internal/configrefresh/classify.go:340`).
  - `apps/edge/internal/transport/server.go:320` always attaches a full `NodeConfigPayload`, and current Node behavior reports any non-nil config as `restart_required` (`apps/node/internal/node/node.go:274`).
  - Therefore an apply refresh with an identical config and a connected node can report node `restart_required` even though no config changed and no node payload update is needed.
- Affected files: `apps/edge/internal/bootstrap/runtime.go`, `apps/edge/internal/bootstrap/runtime_test.go`; optionally `apps/edge/internal/transport/server.go` only if the no-op behavior is better handled at the transport request builder.
- Verification evidence from this review: `go test -count=1 ./apps/edge/internal/transport ./apps/edge/internal/bootstrap`, `go test -count=1 ./apps/node/internal/transport ./apps/node/internal/node`, `make proto`, and `go test -count=1 ./packages/go/... ./proto/gen/...` all passed, so the blocker is an uncovered edge case rather than an existing test failure.
- Narrow reread allowed: the two current archived log files listed above.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/update-plane-self-update-foundation/milestones/runtime-reconnect-config-refresh.md`
- Task ids:
  - `push-contract`: Edge-to-Node config refresh message 또는 command 계약이 proto/config 구조에 추가되고 backward compatibility 기준이 정리된다. 검증: generated proto와 handler 테스트가 통과한다.
- Completion mode: check-on-pass

## 범위 결정 근거

- 포함: no-change apply refresh에서 node push를 하지 않거나 true no-op response가 나오도록 gate를 추가하고, connected-node regression test로 확인한다.
- 제외: provider capacity change에 대한 current phase node result 정책, Node adapter live diff, in-flight drain, runtime concurrency/workspace live apply, proto schema 변경. 이 follow-up은 이미 추가된 push contract를 좁게 보정한다.
- lane/GNN: `local-G05`. 수정 범위가 Edge refresh apply gate와 targeted regression test로 한정되고, 정확성은 deterministic Go tests로 검증할 수 있다.

## 구현 체크리스트

- [ ] no-change apply refresh가 connected node에 full `NodeConfigPayload`를 push해 `restart_required` node_result를 만들지 않도록 gate를 추가한다. 권장 기준: `len(result.Changes)==0`이면 local runtime snapshot은 유지/동일화하더라도 `PushConfigRefresh`를 호출하지 않고 `NodeResults`는 empty로 둔다.
- [ ] connected fake node를 둔 regression test를 추가하거나 기존 runtime refresh test를 확장해, identical candidate config apply에서 fake node가 `NodeConfigRefreshRequest`를 받지 않거나 node result가 no-op/applied로 남는지 확인한다. 테스트는 기존 provider capacity refresh push 테스트의 request_id/payload assertion을 약화하지 않는다.
- [ ] 회귀 검증으로 `go test -count=1 ./apps/edge/internal/transport ./apps/edge/internal/bootstrap`, `go test -count=1 ./apps/node/internal/transport ./apps/node/internal/node`, `make proto`, `go test -count=1 ./packages/go/... ./proto/gen/...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- 이 follow-up은 동일 split subtask `04+02_config_push_contract` 안의 FAIL 보완이다.
- 새 archive evidence는 현재 task 디렉터리의 `plan_cloud_G07_2.log`와 `code_review_cloud_G07_2.log`만 좁게 참조한다.
- PASS 전에는 후속 `05+04_node_adapter_diff`와 `06+05_node_drain_runtime_config`를 진행하지 않는다.

### [REVIEW_REVIEW_REVIEW_NCPUSH-1] No-Change Refresh Push Gate

#### 문제

`Runtime.RefreshConfig`는 apply mode에서 `result.Status == StatusApplied`이면 `applyMutableConfig`를 호출한다. 하지만 no-change config도 `StatusApplied`다. 이후 `PushConfigRefresh`는 full config payload를 붙이고, Node는 non-nil config를 current phase에서 `restart_required`로 응답한다.

Before:

```go
if req.Mode == configrefresh.ModeApply && result.Status == configrefresh.StatusApplied && candidate != nil {
    nodeResults, err := r.applyMutableConfig(ctx, candidate, result.Changes, req.RequestID)
    ...
}
```

#### 해결 방법

no-change apply를 node push 대상에서 제외한다. 가장 작은 수정은 `len(result.Changes)==0`일 때 `PushConfigRefresh`를 호출하지 않도록 `applyMutableConfig` 또는 호출부에 gate를 두는 것이다. Runtime config snapshot을 candidate로 갱신할지 여부는 기존 no-change semantics와 일관되게 처리하되, connected Node에 full payload를 보내 restart_required를 만들면 안 된다.

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

- [ ] `apps/edge/internal/bootstrap/runtime.go`: no-change apply에서 node refresh push를 건너뛰도록 gate를 추가한다.
- [ ] `apps/edge/internal/bootstrap/runtime_test.go`: connected fake node가 있는 identical candidate apply regression을 추가한다.

#### 중간 검증

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

## 최종 검증

```bash
go test -count=1 ./apps/edge/internal/transport ./apps/edge/internal/bootstrap
go test -count=1 ./apps/node/internal/transport ./apps/node/internal/node
make proto
go test -count=1 ./packages/go/... ./proto/gen/...
```

기대 결과: targeted Edge/Node tests와 proto/platform-common 검증이 모두 PASS하고, no-change apply regression이 node restart-required false positive를 막는 증거를 제공한다.
