<!-- task=14+ops_command_smoke plan=0 tag=E2ECMD -->

# Plan - E2ECMD

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

**필수: 구현 마지막에는 반드시 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 명령 출력으로 채운다.** 이 task는 E2E smoke가 `/capabilities`, `/sessions`, `/transport` 명령을 실제 console path로 검증하게 만드는 작업이다. 구현 체크리스트와 review stub의 구현 체크리스트를 모두 완료하고, 중간/최종 검증 출력은 실제 stdout/stderr로 남긴다. review 파일의 `이 파일을 읽는 리뷰 에이전트에게` 아카이브 지시와 `코드리뷰 전용 체크리스트`는 코드리뷰 skill 전용이므로 구현 에이전트가 실행하거나 수정하지 않는다.

## 배경

07번 이후 ops console에는 `/capabilities`, `/sessions`, `/transport`가 구현되어 있지만 `scripts/e2e-smoke.sh`는 아직 `/nodes`, prompt, session/background, terminate, optional `/status`만 실행한다. 새 command surface는 unit test가 있어도 bin-level console wiring과 output formatting이 깨질 수 있다. smoke는 실제 edge/node binary를 쓰므로 이 경로를 최소 한 번씩 통과해야 한다.

## 의존 관계 및 구현 순서

- 선행 task: `agent-task/14_e2e_temp_workspace/complete.log`가 있어야 시작한다.
- 이유: 두 task 모두 `scripts/e2e-smoke.sh`를 수정하며, 이 task의 final smoke는 root `iop.db` 격리 fix 위에서 실행되어야 한다.
- `agent-task/13_transport_idle_heartbeat`, `agent-task/15_node_command_docs_accuracy`와는 독립이다.

## 분석 결과

### 읽은 파일

- `scripts/e2e-smoke.sh`
- `apps/edge/internal/opsconsole/console.go`
- `apps/edge/internal/opsconsole/status.go`
- `apps/edge/internal/opsconsole/console_test.go`
- `apps/edge/internal/opsconsole/events_test.go`
- `apps/edge/internal/service/service.go`
- `apps/edge/internal/service/service_test.go`
- `apps/node/internal/node/node.go`
- `apps/node/internal/adapters/cli/cli.go`

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

- `/capabilities` real console smoke: unit tests cover formatting/service path, but script does not send command. 공백 있음.
- `/sessions` real console smoke: unit tests cover command handling, but script does not verify output from real persistent CLI session map. 공백 있음.
- `/transport` real console smoke: unit tests cover node command response, but bin-level command path is not in smoke. 공백 있음.

### 심볼 참조

- renamed/removed symbol 없음.
- 새 shell assertion helper가 필요하면 call site는 `scripts/e2e-smoke.sh` 내부로 제한한다.

### 범위 결정 근거

- ops console command implementation은 이미 존재하므로 Go service/node logic은 변경하지 않는다.
- output format은 `apps/edge/internal/opsconsole/status.go:88-101`의 stable sorted map rendering을 기준으로 검증한다.
- `/status` real CLI behavior와 heartbeat idle 문제는 이 task 범위가 아니다.

### 빌드 등급

- Build `cloud-G07`: shell/bin orchestration, stdout parsing, real console command smoke가 포함된다.
- Review `cloud-G07`: E2E output assertions가 실제 command 결과를 잡는지 재실행 확인이 필요하다.

## 구현 체크리스트

- [ ] [E2ECMD-1] `scripts/e2e-smoke.sh` command sequence에 `/capabilities`, `/transport`, `/sessions`를 추가한다.
- [ ] [E2ECMD-2] smoke assertions에 세 command의 stable output marker를 추가한다.
- [ ] 중간 검증과 최종 검증 명령을 모두 실행하고 실제 stdout/stderr를 CODE_REVIEW에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [E2ECMD-1] ops console command sequence 확장

#### 문제

`scripts/e2e-smoke.sh:148-162`는 node 등록 후 `/nodes`, prompt, session/background, terminate, optional `/status`만 보낸다. `/capabilities`, `/sessions`, `/transport`는 `apps/edge/internal/opsconsole/console.go:64` help text와 switch cases `console.go:118-129`에 존재하지만 smoke에서 실행되지 않는다.

Before:

```bash
# scripts/e2e-smoke.sh:148
sleep 3
send_cmd "/nodes"
send_cmd "$STRICT_PROMPT"
send_cmd "/session session2"
send_cmd "/background on"
send_cmd "Reply with exactly 'OK' again."
send_cmd "/background off"
```

After:

```bash
sleep 3
send_cmd "/nodes"
send_cmd "/capabilities"
send_cmd "/transport"
send_cmd "$STRICT_PROMPT"
send_cmd "/session session2"
send_cmd "/background on"
send_cmd "Reply with exactly 'OK' again."
send_cmd "/background off"
send_cmd "/sessions"
```

#### 해결 방법

- `/capabilities`와 `/transport`는 첫 prompt 전에 실행해 node command path를 먼저 확인한다.
- `/sessions`는 `session2` background run 이후, terminate 전 실행한다. persistent mock에서는 `session2`가 session list에 나타나야 한다.
- real profile에서도 temp config가 profile 하나만 노출하므로 `/capabilities` target list는 `$TARGET` 기준으로 검증 가능하다.

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

- [ ] `scripts/e2e-smoke.sh` command sequence 수정
- [ ] `/sessions`가 persistent terminate보다 먼저 실행되도록 유지
- [ ] optional `/status` 순서는 기존처럼 마지막에 유지

#### 테스트 작성

- shell smoke가 test다. 별도 Go unit test는 추가하지 않는다.

#### 중간 검증

```bash
bash -n scripts/e2e-smoke.sh
IOP_E2E_IDLE_SECONDS=2 make test-e2e
```

Expected: mock smoke PASS, output에 `[node-test-node-capabilities]`, `[node-test-node-transport]`, `[node-test-node-sessions]`가 있다.

### [E2ECMD-2] stable output assertion 추가

#### 문제

`scripts/e2e-smoke.sh:222-227`의 current assertions는 command-specific output을 확인하지 않는다. `apps/edge/internal/opsconsole/status.go:88-101`는 command result를 `[node-{label}-{command}]` header와 sorted `key = value` lines로 출력하므로 deterministic assertion이 가능하다.

Before:

```bash
check_grep "test-node" "$EDGE_OUT" "node registration not found"
check_grep "start run_id=" "$EDGE_OUT" "run start not found"
check_grep "complete run_id=" "$EDGE_OUT" "run completion not found"
```

After:

```bash
check_grep "\\[node-test-node-capabilities\\]" "$EDGE_OUT" "/capabilities output not found"
check_grep "adapter = cli" "$EDGE_OUT" "/capabilities adapter not found"
check_grep "\\[node-test-node-transport\\]" "$EDGE_OUT" "/transport output not found"
check_grep "connected = true" "$EDGE_OUT" "/transport connected status not found"
check_grep "\\[node-test-node-sessions\\]" "$EDGE_OUT" "/sessions output not found"
```

#### 해결 방법

- `grep` basic regex escaping에 맞게 `[`와 `]`를 escape한다.
- `targets = $TARGET` assertion은 real profile target과 mock target 모두에서 안정적이면 추가한다. profile block이 target 하나만 포함되므로 추가 가능해야 한다.
- `/sessions`는 non-persistent profile에서 count 0일 수 있으므로 header 존재는 필수, specific session entry는 persistent mock에서만 조건부로 검증한다.

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

- [ ] `scripts/e2e-smoke.sh`에 capabilities header/assertion 추가
- [ ] `scripts/e2e-smoke.sh`에 transport header/assertion 추가
- [ ] `scripts/e2e-smoke.sh`에 sessions header/assertion 추가
- [ ] persistent mock이면 `persistent:fake-cat/session2` 또는 equivalent session entry를 검증

#### 테스트 작성

- shell smoke 검증으로 대체한다. `apps/edge/internal/opsconsole/status.go` sorted formatting은 기존 Go tests가 다룬다.

#### 중간 검증

```bash
IOP_E2E_IDLE_SECONDS=2 make test-e2e > /tmp/iop-e2ecmd-smoke.log 2>&1
grep -E "\\[node-test-node-(capabilities|transport|sessions)\\]|adapter = cli|connected = true|sessions =" /tmp/iop-e2ecmd-smoke.log
```

Expected: command headers and key outputs are printed. smoke exits 0.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `scripts/e2e-smoke.sh` | E2ECMD-1, E2ECMD-2 |
| `agent-task/14+ops_command_smoke/CODE_REVIEW-cloud-G07.md` | evidence 기록 |

## 최종 검증

```bash
test -f agent-task/14_e2e_temp_workspace/complete.log
bash -n scripts/e2e-smoke.sh
IOP_E2E_IDLE_SECONDS=2 make test-e2e > /tmp/iop-e2ecmd-smoke.log 2>&1
grep -E "\\[node-test-node-(capabilities|transport|sessions)\\]|adapter = cli|connected = true|sessions =" /tmp/iop-e2ecmd-smoke.log
go test -count=1 ./apps/edge/internal/opsconsole ./apps/edge/internal/service ./apps/node/internal/node ./apps/node/internal/adapters/cli
git diff --check
```

Expected: all commands exit 0. Go test cache output은 `-count=1` 때문에 허용하지 않는다. The grep output must include all three node command headers.

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