<!-- task=m-runtime-reconnect-config-refresh/07+01,03,04,05,06_field_docs_smoke plan=5 tag=REVIEW_REVIEW_REVIEW_REVIEW_TEST -->

# Plan - REVIEW_REVIEW_REVIEW_REVIEW_TEST

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

`CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 반드시 채운다. 검증 명령을 실행하고 실제 stdout/stderr를 붙여 넣은 뒤 active 파일을 그대로 두고 review ready로 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다. 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션을 채우고 멈춘다. 외부 환경/secret/service blocker, 일반 범위 조정, 증거 공백은 사용자 리뷰가 아니라 검증 결과나 후속 plan으로 기록한다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다.

## 배경

직전 루프는 S16 reconnect evidence와 refresh mechanism evidence는 만들었지만, S17의 핵심 조건인 `qwen3.6:35b` 동시 4개 요청 성공이 2/4에 그쳤다. 최종 검증은 line count만 assert해 `FINAL_VERIFICATION=PASS`를 출력했으므로 verification trust도 깨졌다. 이번 follow-up은 OneXPlayer backend reachability를 먼저 증명한 뒤 S17 concurrent evidence만 좁게 재수집하고, 4개 row 모두 성공할 때만 Roadmap Targets를 닫는다.

## 사용자 리뷰 요청 흐름

선택된 Milestone lock 결정만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. OneXPlayer/GX10/원격 runner 접근, secret/service 준비, field evidence 공백은 user-review gate가 아니라 검증 blocker 또는 follow-up evidence다. 구현 중 직접 사용자에게 묻지 말고, code-review가 요청을 검증한 뒤 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Archive Evidence Snapshot

- Prior active task: `agent-task/m-runtime-reconnect-config-refresh/07+01,03,04,05,06_field_docs_smoke`
- Current archived plan/review:
  - `agent-task/m-runtime-reconnect-config-refresh/07+01,03,04,05,06_field_docs_smoke/plan_cloud_G08_4.log`
  - `agent-task/m-runtime-reconnect-config-refresh/07+01,03,04,05,06_field_docs_smoke/code_review_cloud_G08_4.log`
- Verdict: FAIL.
- Required summary:
  - S17 remains incomplete. `code_review_cloud_G08_4.log` records that final verification printed `FINAL_VERIFICATION=PASS` even though `refresh-concurrent-4.jsonl` had only `concurrent_ok=2/4`.
  - Reviewer recheck of remote `/tmp/iop-field-evidence/refresh-concurrent-4.jsonl` confirmed 4 rows, 2 success rows, and 2 `502` rows caused by `192.168.0.59:13305` being unreachable.
  - Final verification must assert every concurrent row has `ok=true`, `code=200`, and `choices>0`; line count alone is insufficient.
- Affected files/areas:
  - `CODE_REVIEW-cloud-G08.md`
  - Remote `/tmp/iop-field-evidence/refresh-concurrent-4*.jsonl`
  - Remote dev-runtime `http://toki-labs.com:18083/v1`
  - OneXPlayer Lemonade backend `192.168.0.59:13305`
- Roadmap carryover:
  - `field-reconnect`: prior loop evidence shows 3 nodes reconnect after Edge restart with `bootstrap_rerun=none`.
  - `docs`: tracked docs and field smoke criteria now describe reconnect/config refresh flow.
  - `field-refresh`: still incomplete until all 4 concurrent OpenAI-compatible requests succeed.
- Narrow archive reread allowed if needed: only the two current archived logs listed above.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/update-plane-self-update-foundation/milestones/runtime-reconnect-config-refresh.md`
- Task ids:
  - `field-reconnect`: `toki-labs.com` dev-runtime에서 Edge restart 후 GX10 Linux Node와 OneXPlayer Windows Node가 bootstrap 재실행 없이 재접속한다.
  - `field-refresh`: `gx10-vllm=4`, `onexplayer-lemonade=3` capacity 변경을 refresh로 반영하고 Edge process restart 없이 `qwen3.6:35b` 동시 4개 요청이 성공한다.
  - `docs`: bootstrap/reconnect/refresh 운영 방법과 제한 사항이 `apps/edge/README.md`, `docs/edge-local-dev-guide.md`, agent-test field 기준에 반영된다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-roadmap/current.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-contract/index.md`
- `agent-contract/provided/openai-compatible-api.md`
- `agent-roadmap/phase/update-plane-self-update-foundation/milestones/runtime-reconnect-config-refresh.md`
- `agent-roadmap/sdd/update-plane-self-update-foundation/runtime-reconnect-config-refresh/SDD.md`
- `apps/edge/README.md`
- `docs/edge-local-dev-guide.md`
- `agent-task/m-runtime-reconnect-config-refresh/07+01,03,04,05,06_field_docs_smoke/plan_cloud_G08_4.log`
- `agent-task/m-runtime-reconnect-config-refresh/07+01,03,04,05,06_field_docs_smoke/code_review_cloud_G08_4.log`
- `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/01_reconnect_supervisor/complete.log`
- `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/03+02_edge_refresh_apply/complete.log`
- `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/04+02_config_push_contract/complete.log`
- `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/05+04_node_adapter_diff/complete.log`
- `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/06+05_node_drain_runtime_config/complete.log`

### SDD 기준

- SDD: `agent-roadmap/sdd/update-plane-self-update-foundation/runtime-reconnect-config-refresh/SDD.md`
- 상태: `[승인됨]`, SDD 잠금 해제, 사용자 리뷰 없음.
- Target scenarios:
  - S16 -> `field-reconnect`: dev-runtime 3개 Node가 Edge restart 후 bootstrap 재실행 없이 회복.
  - S17 -> `field-refresh`: `gx10-vllm=4`, `onexplayer-lemonade=3` refresh apply 후 Edge restart 없이 `qwen3.6:35b` 동시 4개 요청 성공.
  - S18 -> `docs`: tracked docs와 field smoke 기준 반영.
- Evidence Map rows:
  - S16: remote dev field status와 TCP connection evidence.
  - S17: remote dev 4 concurrent OpenAI-compatible requests와 refresh result evidence.
  - S18: docs diff와 agent-test field rule diff.
- 이번 checklist는 S16/S18 evidence를 carryover로 재확인하고, S17은 4개 row 모두 성공하는 새 remote evidence를 요구한다.

### 테스트 환경 규칙

- `test_env=local`.
- `agent-test/local/rules.md` present/read.
- Matched profile: `agent-test/local/edge-smoke.md`.
- 적용 기준: remote runner `ssh toki@toki-labs.com`, clean review root `/Users/toki/agent-work/iop-field-review`, evidence dir `/tmp/iop-field-evidence`, dev-runtime Edge id `edge-toki-labs-dev`, Control Plane status `http://127.0.0.1:18001`, Edge OpenAI-compatible `http://toki-labs.com:18083/v1`, Edge-Node TCP `toki-labs.com:18084`, GX10 `192.168.0.91`, OneXPlayer backend `192.168.0.59:13305`, model alias `qwen3.6:35b`.
- 테스트 환경 프리플라이트:
  - 현재 local runtime source는 `preflight-local.txt` 기준 clean이어야 한다.
  - remote evidence는 `/tmp/iop-field-evidence`에서 확인한다.
  - OneXPlayer backend TCP reachability를 S17 rerun 전에 확인한다.
  - OneXPlayer가 여전히 unreachable이면 CP/Edge/Node process를 재시작하지 말고 blocker stdout/stderr만 review stub에 기록한다.
  - token/API key/private endpoint 원문은 tracked docs, roadmap, review output에 기록하지 않는다.

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

- `field-reconnect`: 자동 테스트가 아니라 remote evidence로 판정한다. 직전 loop evidence가 존재하므로 follow-up은 파일 존재와 3-node status/TCP carryover를 확인한다.
- `field-refresh`: S17 concurrent success evidence가 없다. 4 row 모두 success인 새 remote JSONL이 필요하다.
- `docs`: tracked docs와 `agent-test/local/edge-smoke.md` 기준은 존재한다. follow-up은 변경을 추가하지 않고 final verification에서 문서 기준과 field evidence가 모순되지 않는지만 확인한다.

### 심볼 참조

- none. 이 follow-up은 Go symbol rename/remove를 하지 않는다.

### 분할 판단

- 기존 split subtask `07+01,03,04,05,06_field_docs_smoke`를 유지한다.
- Predecessor completion:
  - `01`: `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/01_reconnect_supervisor/complete.log`
  - `03`: `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/03+02_edge_refresh_apply/complete.log`
  - `04`: `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/04+02_config_push_contract/complete.log`
  - `05`: `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/05+04_node_adapter_diff/complete.log`
  - `06`: `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/06+05_node_drain_runtime_config/complete.log`
- 이번 follow-up은 새 subtask로 쪼개지 않는다. 남은 Required가 S17 remote evidence trust 1건이고, 같은 field evidence chain 안에서만 닫힌다.

### 범위 결정 근거

- Go source와 public API 구현은 수정하지 않는다.
- roadmap/SDD 문서는 code-review가 직접 완료 처리하지 않는다.
- OneXPlayer backend가 offline이면 환경 blocker로 기록하고 user-review gate로 올리지 않는다.
- remote `/Users/toki/agent-work/iop-dev`는 reset/clean/pull/overwrite하지 않는다.

### 빌드 등급

- `cloud-G08`: 원격 runner, field device reachability, process/runtime evidence, OpenAI-compatible 동시 요청, evidence trust diagnosis가 중심이라 local bounded 조건을 만족하지 않는다.

## 구현 체크리스트

- [ ] `REVIEW_REVIEW_REVIEW_REVIEW_TEST-1` OneXPlayer backend reachability를 먼저 확인한 뒤 S17 concurrent evidence를 재수집한다. 검증: `/tmp/iop-field-evidence/refresh-concurrent-4-rerun.jsonl`의 4개 row가 모두 `ok=true`, `code=200`, `choices>0`이며 final verification이 `FINAL_VERIFICATION=PASS`를 출력한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

OneXPlayer backend TCP reachability가 먼저 성공해야 S17 concurrent rerun을 진행한다. reachability가 실패하면 Edge/Node process를 건드리지 말고 blocker output만 기록한다.

### [REVIEW_REVIEW_REVIEW_REVIEW_TEST-1] S17 Concurrent Success Evidence

문제: `code_review_cloud_G08_4.log`의 FAIL finding은 `FINAL_VERIFICATION=PASS`가 출력됐지만 실제 concurrent success가 2/4였다고 기록한다. SDD S17은 동시 4개 요청 성공을 요구하므로 line count 검증만으로는 완료할 수 없다.

해결 방법: remote runner에서 OneXPlayer backend TCP reachability를 먼저 확인한다. reachable이면 기존 running Edge OpenAI-compatible endpoint로 동시 4개 요청을 다시 보내고, 결과 JSONL을 `refresh-concurrent-4-rerun.jsonl`에 저장한다. 검증 parser는 4개 row 모두 success임을 assert한다.

수정 파일 및 체크리스트:

- [ ] remote `/tmp/iop-field-evidence/onexplayer-backend-preflight.txt`를 생성한다.
- [ ] OneXPlayer backend가 unreachable이면 remote mutation 없이 중단하고 실패 stdout/stderr를 review stub에 기록한다.
- [ ] reachable이면 `/tmp/iop-field-evidence/refresh-concurrent-4-rerun.jsonl`을 새로 생성한다.
- [ ] parser가 `len(rows)==4`, `all(ok is True)`, `all(code==200)`, `all(choices>0)`를 assert한다.
- [ ] S16 carryover 파일과 docs 기준을 final verification에서 함께 확인한다.

테스트 작성: 새 automated test는 작성하지 않는다. 이 항목은 SDD S17 field evidence 재수집이다.

중간 검증:

```bash
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'set -euo pipefail
mkdir -p /tmp/iop-field-evidence
python3 - <<'"'"'PY'"'"' 2>&1 | tee /tmp/iop-field-evidence/onexplayer-backend-preflight.txt
import socket
host = "192.168.0.59"
port = 13305
with socket.create_connection((host, port), timeout=10):
    print(f"onexplayer_backend_reachable={host}:{port}")
PY
'
```

Expected: exit 0 and `onexplayer_backend_reachable=192.168.0.59:13305`. If it fails, stop and record the actual output.

S17 rerun after reachability success:

```bash
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'set -euo pipefail
python3 - <<'"'"'PY'"'"' | tee /tmp/iop-field-evidence/refresh-concurrent-4-rerun.jsonl
import concurrent.futures, json, urllib.error, urllib.request
url = "http://toki-labs.com:18083/v1/chat/completions"
payload = {
    "model": "qwen3.6:35b",
    "messages": [{"role": "user", "content": "Return the word ok."}],
    "max_tokens": 4,
    "stream": False,
}
body = json.dumps(payload).encode()
def one(i):
    req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            data = json.loads(resp.read())
            return {"index": i, "ok": True, "code": resp.status, "model": data.get("model"), "choices": len(data.get("choices", []))}
    except urllib.error.HTTPError as exc:
        return {"index": i, "ok": False, "code": exc.code, "error": exc.read().decode("utf-8", "replace")}
    except Exception as exc:
        return {"index": i, "ok": False, "error": str(exc)}
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as ex:
    for row in ex.map(one, range(4)):
        print(json.dumps(row, sort_keys=True))
PY
python3 - <<'"'"'PY'"'"'
import json
from pathlib import Path
rows = [json.loads(line) for line in Path("/tmp/iop-field-evidence/refresh-concurrent-4-rerun.jsonl").read_text().splitlines()]
assert len(rows) == 4, rows
assert all(row.get("ok") is True for row in rows), rows
assert all(row.get("code") == 200 for row in rows), rows
assert all(int(row.get("choices", 0)) > 0 for row in rows), rows
print("concurrent_success=4/4")
PY
'
```

Expected: exit 0 and `concurrent_success=4/4`.

## 수정 파일 요약

| 파일/영역 | 항목 |
|----------|------|
| `CODE_REVIEW-cloud-G08.md` | REVIEW_REVIEW_REVIEW_REVIEW_TEST-1 |
| Remote `/tmp/iop-field-evidence/onexplayer-backend-preflight.txt` | REVIEW_REVIEW_REVIEW_REVIEW_TEST-1 |
| Remote `/tmp/iop-field-evidence/refresh-concurrent-4-rerun.jsonl` | REVIEW_REVIEW_REVIEW_REVIEW_TEST-1 |

## 최종 검증

```bash
git diff --check
test -s /tmp/iop-field-evidence/preflight-local.txt
grep -q "runtime_source_dirty=no" /tmp/iop-field-evidence/preflight-local.txt
rg --sort path -n "edge-toki-labs($|[^-])|edge-toki-labs[^-]dev|/Users/toki/agent-work/iop-dev.*config refresh" apps/edge/README.md docs/edge-local-dev-guide.md agent-test/local/edge-smoke.md agent-task/m-runtime-reconnect-config-refresh/07+01,03,04,05,06_field_docs_smoke/PLAN-cloud-G08.md agent-task/m-runtime-reconnect-config-refresh/07+01,03,04,05,06_field_docs_smoke/CODE_REVIEW-cloud-G08.md || true
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'set -euo pipefail
for f in preflight-remote.txt preflight-gx10.txt review-config-check.txt review-config-refresh-help.txt reconnect-before-status.json reconnect-before-tcp.txt reconnect-pids.txt reconnect-after-status.json reconnect-after-tcp.txt refresh-dry-run.json refresh-apply.json refresh-pid.txt refresh-healthz.txt refresh-models.json onexplayer-backend-preflight.txt refresh-concurrent-4-rerun.jsonl; do
  test -s "/tmp/iop-field-evidence/$f"
done
grep -q -- "--mode" /tmp/iop-field-evidence/review-config-refresh-help.txt
grep -q "mac-codex-node" /tmp/iop-field-evidence/reconnect-after-status.json
grep -q "gx10-vllm-node" /tmp/iop-field-evidence/reconnect-after-status.json
grep -q "onexplayer-lemonade-node" /tmp/iop-field-evidence/reconnect-after-status.json
grep -q "bootstrap_rerun=none" /tmp/iop-field-evidence/reconnect-pids.txt
grep -q "onexplayer_backend_reachable=192.168.0.59:13305" /tmp/iop-field-evidence/onexplayer-backend-preflight.txt
python3 - <<'"'"'PY'"'"'
import json
from pathlib import Path
p = Path("/tmp/iop-field-evidence/refresh-pid.txt").read_text()
parts = dict(item.split("=", 1) for item in p.split() if "=" in item)
assert parts["edge_pid_before"] == parts["edge_pid_after"], p
rows = [json.loads(line) for line in Path("/tmp/iop-field-evidence/refresh-concurrent-4-rerun.jsonl").read_text().splitlines()]
assert len(rows) == 4, rows
assert all(row.get("ok") is True for row in rows), rows
assert all(row.get("code") == 200 for row in rows), rows
assert all(int(row.get("choices", 0)) > 0 for row in rows), rows
print("field_evidence_ok concurrent_success=4/4")
PY
printf "FINAL_VERIFICATION=PASS field-reconnect field-refresh docs evidence=/tmp/iop-field-evidence review_root=/Users/toki/agent-work/iop-field-review\n"
'
```

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