<!-- task=cli_terminal_cycle plan=2 tag=REVIEW_REVIEW_API -->

# Code Review Reference - REVIEW_REVIEW_API

## 개요

date=2026-05-03
task=cli_terminal_cycle, plan=2, tag=REVIEW_REVIEW_API

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

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료 후 반드시 아래 순서로 아카이브하세요.

1. `CODE_REVIEW.md` → `code_review_N.log` (N = 기존 code_review_*.log 수)
2. `PLAN.md` → `plan_M.log` (M = 기존 plan_*.log 수)
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_REVIEW_API-1] Partial lifecycle start 실패 시 started resources 정리 | [x] |

## 계획 대비 변경 사항

- goroutine의 `close(outputCh)` / `doneCh <- cmd.Wait()` 순서를 뒤집었다. 계획의 non-blocking doneCh select가 drainUntilIdle 직후에 실행될 때, goroutine이 아직 `cmd.Wait()`를 doneCh에 보내지 못한 race 조건이 있었다. doneCh(버퍼 크기 1)에 먼저 send하고 그 다음 outputCh를 close하면 drainUntilIdle이 outputCh close를 보고 반환할 시점에 doneCh가 이미 채워져 있어 race가 없다.

## 주요 설계 결정

- `Registry.Start`: `started []string` 슬라이스로 성공한 adapter를 추적하고, 실패 시 역순 stop.
- `CLI.Start`: profile 실패 시 `c.Stop(context.Background())`로 이미 시작된 session을 정리하고 `c.sessions`를 빈 map으로 초기화.
- `CLI.Stop`: 루프 후 `c.sessions = make(map[string]*profileSession)`를 추가해 멱등성 확보. 두 번 이상 호출해도 panic/double-kill 없음.
- `bootstrap.Module`: `reg.Start` 실패 경로에 `_ = reg.Stop(context.Background())` 추가. Registry.Start가 rollback을 수행하더라도 bootstrap에서 한 번 더 Stop을 호출하는 것은 빈 sessions map 위에서 안전하게 no-op.)

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

- `Registry.Start`가 일부 lifecycle adapter start 성공 후 뒤 adapter start 실패 시 앞 adapter를 역순으로 stop하는지 확인한다.
- `CLI.Start`가 여러 persistent profile 중 뒤 profile start 실패 시 앞 profile process/session을 정리하는지 확인한다.
- `CLI.Stop`이 반복 호출되어도 안전하고 sessions map을 비우는지 확인한다.
- `bootstrap.Module`의 `reg.Start` 실패 경로가 session/store뿐 아니라 registry lifecycle cleanup도 호출하는지 확인한다.
- rollback 테스트가 실제 실패 경로를 재현하고 marker/process cleanup을 의미 있게 검증하는지 확인한다.

## 검증 결과

_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._

### REVIEW_REVIEW_API-1 중간 검증
```
$ go test ./apps/node/internal/adapters ./apps/node/internal/adapters/cli
ok  	iop/apps/node/internal/adapters	0.003s
ok  	iop/apps/node/internal/adapters/cli	0.785s
```

### 최종 검증
```
$ go test ./apps/node/internal/adapters ./apps/node/internal/adapters/cli
ok  	iop/apps/node/internal/adapters	0.003s
ok  	iop/apps/node/internal/adapters/cli	0.785s

$ go test ./apps/node/internal/bootstrap ./apps/node/...
ok  	iop/apps/node/internal/bootstrap	0.160s
ok  	iop/apps/node/internal/adapters	0.003s
ok  	iop/apps/node/internal/adapters/cli	0.785s
ok  	iop/apps/node/internal/node	0.006s
ok  	iop/apps/node/internal/transport	0.005s

$ go test ./...
ok  	iop/apps/edge/internal/node	(cached)
ok  	iop/apps/edge/internal/transport	(cached)
ok  	iop/apps/node/internal/adapters	(cached)
ok  	iop/apps/node/internal/adapters/cli	(cached)
ok  	iop/apps/node/internal/bootstrap	(cached)
ok  	iop/apps/node/internal/node	(cached)
ok  	iop/apps/node/internal/transport	(cached)
ok  	iop/packages/config	(cached)
```

---

## 코드리뷰 결과

### 종합 판정

FAIL

### 차원별 평가

| 차원 | 평가 | 근거 |
|------|------|------|
| correctness | Fail | lifecycle partial-start rollback은 보강됐지만 persistent CLI timeout/cancel 이후 늦은 출력이 다음 run에 섞일 수 있다. |
| completeness | Warn | `Registry.Start`, `CLI.Start`, bootstrap failure cleanup은 계획대로 반영됐으나 `Registry.Stop`은 첫 stop error에서 중단된다. |
| test coverage | Warn | rollback 테스트가 sessions map 초기화는 확인하지만 실제 process cleanup을 marker로 검증하지 않고, map iteration 순서 때문에 partial-start 경로를 항상 강제하지 않는다. |
| API contract | Pass | 기존 `runtime.Adapter` 계약과 optional lifecycle 방식은 유지된다. |
| code quality | Warn | cleanup 책임이 추가됐지만 persistent session invalidation과 stop error aggregation이 아직 명확하지 않다. |
| verification trust | Pass | 리뷰어가 기록된 범위의 `go test`와 `go test ./...`를 재실행했고 모두 통과했다. |

### 발견된 문제

- Required: `apps/node/internal/adapters/cli/cli.go:221`에서 persistent process에 prompt를 쓴 뒤 `apps/node/internal/adapters/cli/cli.go:237-238`의 `ctx.Done()` 경로는 session을 정리하거나 출력 큐를 무효화하지 않고 바로 반환한다. `apps/node/internal/node/node.go:91-100`에서 RunRequest timeout/cancel이 이 context로 전달되므로, timeout된 run의 늦은 출력이 `sess.output`에 남고 다음 `Execute`가 `apps/node/internal/adapters/cli/cli.go:239-249`에서 새 `RunID`의 delta로 방출할 수 있다. persistent CLI는 run별 출력 경계가 idle timeout뿐이라 cancel/timeout 시에는 session을 kill/remove/restart하는 식으로 오염을 끊어야 한다. 회귀 테스트는 첫 run을 timeout시킨 뒤 늦은 `reply:first`가 두 번째 run 이벤트에 섞이지 않는지 확인해야 한다.
- Required: `apps/node/internal/adapters/registry.go:88-97`의 `Registry.Stop`은 한 lifecycle adapter의 `Stop`이 error를 반환하면 즉시 반환한다. 이러면 reverse order에서 뒤쪽 adapter stop이 실패할 때 앞쪽 adapter는 stop 호출을 받지 못해 process/session이 남을 수 있다. `Stop`은 모든 lifecycle adapter에 stop을 시도하고 `errors.Join` 또는 first error로 결과를 반환해야 한다.
- Recommended: `apps/node/internal/adapters/cli/cli_test.go:224-266`의 partial profile rollback 테스트는 `Profiles` map 순서에 따라 fail profile이 먼저 실행될 수 있고, 이 경우 이미 시작된 process cleanup 경로를 실제로 밟지 않아도 통과한다. 또한 계획에 있던 marker/exit marker 기반 process cleanup 검증이 없다. CLI start 순서를 deterministic하게 만들거나 테스트 helper를 두고, 시작된 profile이 있다면 종료 marker까지 확인해야 한다.

### 리뷰어 검증

```
$ go test ./apps/node/internal/adapters ./apps/node/internal/adapters/cli ./apps/node/internal/bootstrap ./apps/node/...
ok  	iop/apps/node/internal/adapters	0.003s
ok  	iop/apps/node/internal/adapters/cli	0.785s
ok  	iop/apps/node/internal/bootstrap	0.160s
ok  	iop/apps/node/internal/node	(cached)
ok  	iop/apps/node/internal/transport	(cached)

$ go test ./...
ok  	iop/apps/edge/internal/node	(cached)
ok  	iop/apps/edge/internal/transport	(cached)
ok  	iop/apps/node/internal/adapters	(cached)
ok  	iop/apps/node/internal/adapters/cli	(cached)
ok  	iop/apps/node/internal/bootstrap	(cached)
ok  	iop/apps/node/internal/node	(cached)
ok  	iop/apps/node/internal/transport	(cached)
ok  	iop/packages/config	(cached)
```

### 다음 단계

FAIL: 위 Required 항목을 반영하는 새 `PLAN.md`와 `CODE_REVIEW.md` 스텁을 작성해 후속 구현 루프를 계속한다.
