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

# Plan - REVIEW_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`을 호출하지 않는다.

## 배경

직전 루프에서 실제 OneXPlayer Windows host에 `onexplayer-lemonade-node`를 붙이고 S17 `qwen3.6:35b` 동시 4요청은 4/4 성공으로 회복됐다. 하지만 같은 루프의 Edge restart 재수집 결과는 `reconnect-after-status.json`에 `mac-codex-node`, `gx10-vllm-node`만 남았고, Windows Node process는 살아 있는데 Edge-Node TCP `18084` 연결은 회복되지 않았다. SDD S16과 Milestone `field-reconnect`는 Edge restart 뒤 3개 Node가 bootstrap 재실행 없이 회복되어야 하므로, 이번 follow-up은 Windows Node reconnect failure를 원인 진단하고 필요하면 Node reconnect 구현을 고친 뒤 field evidence를 다시 수집한다.

## 사용자 리뷰 요청 흐름

선택된 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_5.log`
  - `agent-task/m-runtime-reconnect-config-refresh/07+01,03,04,05,06_field_docs_smoke/code_review_cloud_G08_5.log`
- Verdict: FAIL.
- Required summary:
  - S16 remains incomplete. `code_review_cloud_G08_5.log` records that actual OneXPlayer Windows Node was connected before Edge restart, but after Edge restart `reconnect-after-status.json` had only `mac-codex-node` and `gx10-vllm-node`.
  - Reviewer recheck of remote `/tmp/iop-field-evidence/reconnect-after-status.json` and `reconnect-after-tcp.txt` confirmed the after-restart evidence has only 2 connected Nodes / 2 TCP connections.
  - S17 currently has remote `/tmp/iop-field-evidence/refresh-concurrent-4-rerun.jsonl` with 4 rows, all `ok=true`, `code=200`, `choices>0`; keep it as carryover but rerun final verification after S16 is fixed.
- Affected files/areas:
  - `apps/node/internal/bootstrap/module.go`
  - `apps/node/internal/transport/session.go`
  - `apps/node/internal/transport/client.go`
  - `apps/node/internal/bootstrap/module_test.go`
  - remote Windows Node logs under `C:\Users\r0bin\iop-field\`
  - remote `/tmp/iop-field-evidence/reconnect-*.json`, `reconnect-*.txt`, `refresh-concurrent-4-rerun.jsonl`
- Roadmap carryover:
  - `field-reconnect`: incomplete until after-restart status/TCP evidence shows 3 Nodes without manual Node restart/bootstrap rerun.
  - `field-refresh`: current remote JSONL recheck shows 4/4 concurrent success; keep final verification assertion.
  - `docs`: tracked docs and field smoke criteria already describe direct Windows host bootstrap/reconnect/refresh flow; do not add token/private secret values.
- 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/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/node/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/node/internal/bootstrap/module.go`
- `apps/node/internal/transport/session.go`
- `apps/node/internal/transport/client.go`
- `agent-task/m-runtime-reconnect-config-refresh/07+01,03,04,05,06_field_docs_smoke/plan_cloud_G08_5.log`
- `agent-task/m-runtime-reconnect-config-refresh/07+01,03,04,05,06_field_docs_smoke/code_review_cloud_G08_5.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 기준 반영.
- 이번 루프는 S16을 직접 닫고, S17/S18은 carryover와 최종 검증으로 재확인한다.

### 테스트 환경 규칙

- `test_env=local`.
- Field/bootstrap 검증은 원격 runner `ssh toki@toki-labs.com`, dev-runtime repo `/Users/toki/agent-work/iop-dev`, evidence dir `/tmp/iop-field-evidence`, Edge id `edge-toki-labs-dev`, OpenAI-compatible `http://toki-labs.com:18083/v1`, Edge-Node TCP `toki-labs.com:18084` 기준이다.
- OneXPlayer Node는 macOS runner proxy process로 검증하지 않는다. `ssh -J toki@toki-labs.com r0bin@192.168.0.59`로 Windows host에 직접 접속해 PowerShell bootstrap/scheduled task 상태와 로그를 확인한다.
- token/API key/private endpoint 원문은 tracked docs, roadmap, review output에 기록하지 않는다.

### 범위 결정 근거

- S16 failure가 field 환경만의 타이밍 문제인지, Windows Node transport disconnect 감지 문제인지, Node reconnect supervisor 문제인지 아직 확정되지 않았다. 따라서 먼저 timed evidence와 Windows Node log를 수집한다.
- timed evidence로 정상 reconnect가 확인되면 소스 수정 없이 evidence와 final verification만 보강한다.
- timed evidence에서도 실패하면 `apps/node/internal/bootstrap/**` 또는 `apps/node/internal/transport/**`에 최소 수정과 deterministic test를 추가한다.
- Edge/Control Plane/Node process를 무작정 재시작하거나 Windows scheduled task를 수동 재시작해 성공으로 간주하지 않는다. 수동 restart는 운영 복구 evidence일 수 있지만 S16 완료 evidence가 아니다.
- roadmap/SDD 완료 처리는 code-review가 직접 하지 않는다.

### 빌드 등급

- `cloud-G08`: 원격 runner, 실제 Windows host, Edge restart, TCP/process/log evidence, OpenAI-compatible concurrent smoke, evidence trust diagnosis가 중심이라 local bounded 조건을 만족하지 않는다.

## 구현 체크리스트

- [ ] `REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-1` Windows OneXPlayer Node가 Edge restart 후 자동 재접속하지 않는 원인을 진단하고 필요하면 Node reconnect 구현을 수정한다. 검증: timed watcher와 Windows Node log가 disconnect 감지 및 retry/registration 흐름을 설명하고, 관련 Go 테스트가 통과한다.
- [ ] `REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-2` S16/S17/S18 최종 field evidence를 재수집한다. 검증: Edge restart 후 `reconnect-after-status.json`과 `reconnect-after-tcp.txt`가 3개 Node 회복을 증명하고, `refresh-concurrent-4-rerun.jsonl` 4개 row가 모두 성공하며, final verification이 `FINAL_VERIFICATION=PASS`를 출력한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

먼저 remote/Windows timed evidence를 수집한다. 실패 원인이 구현 문제로 확인된 경우에만 소스를 수정하고 테스트를 추가한다. 그 다음 dev-runtime artifact를 원격 runner에 반영한 뒤 Edge restart field evidence와 S17 concurrent evidence를 재수집한다.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-1] Windows Node Reconnect Failure

문제: 직전 루프의 실제 OneXPlayer Windows Node evidence에서 Edge restart 후 Windows Node process는 살아 있었지만 Edge-Node TCP connection이 회복되지 않았다. S16은 bootstrap 재실행 없이 3개 Node가 회복되어야 한다.

해결 방법: Windows Node의 로그와 remote status를 timestamp와 함께 수집해 disconnect listener, reconnect attempt, registration success/failure 중 어디서 멈추는지 확인한다. 구현 문제가 확인되면 `apps/node/internal/bootstrap/module.go`, `apps/node/internal/transport/session.go`, `apps/node/internal/transport/client.go` 중 필요한 최소 범위만 수정하고 regression test를 추가한다.

수정 파일 및 체크리스트:

- [ ] remote `/tmp/iop-field-evidence/onexplayer-reconnect-diagnosis.txt`에 Windows Node process id, log tail, Edge restart 전/후 status/TCP watcher, reconnect attempt 로그를 token 없이 저장한다.
- [ ] `apps/node/internal/bootstrap/module.go`의 reconnect loop가 remote disconnect를 받고 다음 session으로 갱신하는지 확인한다.
- [ ] `apps/node/internal/transport/session.go`의 disconnect listener가 Windows idle/stale TCP에서도 `Done()`을 닫는지 확인한다.
- [ ] 필요 시 `apps/node/internal/bootstrap/module_test.go` 또는 `apps/node/internal/transport/*_test.go`에 regression test를 추가한다.
- [ ] 구현 수정이 있었다면 원격 dev-runtime artifact를 rebuild/sync하고 Windows Node를 새 artifact로 다시 띄운다.

테스트 작성: 구현 수정이 있으면 해당 node package regression test를 추가한다. 수정이 없고 field timing/evidence 보강만으로 닫히면 새 Go test는 생략 가능하지만, 생략 이유를 review stub에 기록한다.

중간 검증:

```bash
go test -count=1 ./apps/node/internal/bootstrap ./apps/node/internal/transport
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'test -s /tmp/iop-field-evidence/onexplayer-reconnect-diagnosis.txt && sed -n "1,220p" /tmp/iop-field-evidence/onexplayer-reconnect-diagnosis.txt'
```

Expected: Go tests pass if code changed; diagnosis explains whether the Windows Node observed disconnect, attempted reconnect, and registered again.

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-2] Final Field Evidence

문제: 직전 final verification did not produce `FINAL_VERIFICATION=PASS`, and after-restart reconnect evidence only had 2 Nodes. S17 4/4 success now exists but must be asserted together with S16 and docs criteria.

해결 방법: 실제 OneXPlayer Windows Node가 connected 상태인 것을 확인한 뒤 Edge restart를 수행하고, retry window를 충분히 관찰한다. 수동 Windows scheduled task restart 없이 after-restart Control Plane status와 TCP evidence가 3개 Node를 보여야 한다. 이후 S17 concurrent JSONL과 docs criteria를 한 final verification에서 함께 assert한다.

수정 파일 및 체크리스트:

- [ ] Edge restart 전 `reconnect-before-status.json`에 3개 Node connected를 저장한다.
- [ ] Edge restart 후 최소 retry interval을 포함하는 timed watcher를 저장하고 `reconnect-after-status.json`에 3개 Node connected를 저장한다.
- [ ] `reconnect-after-tcp.txt`에 Edge-side established TCP connection 3개를 저장한다.
- [ ] `reconnect-pids.txt`에 `bootstrap_rerun=none`과 Edge PID before/after를 저장한다.
- [ ] `refresh-concurrent-4-rerun.jsonl`을 유지하거나 재수집하고 4개 row 모두 `ok=true`, `code=200`, `choices>0`를 assert한다.
- [ ] tracked docs/roadmap/review output에 token/API key/private endpoint 원문이 없는지 확인한다.

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

최종 검증:

```bash
git diff --check
rg --sort path -n "IOP_NODE_TOKEN|Bearer [A-Za-z0-9._-]+|<redacted-token>|node-onexplayer-lemonade.yaml serve" apps/edge/README.md docs/edge-local-dev-guide.md agent-test/local/edge-smoke.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 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 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 onexplayer-reconnect-diagnosis.txt; do
  test -s "/tmp/iop-field-evidence/$f"
done
python3 - <<'"'"'PY'"'"'
import json
from pathlib import Path
base = Path("/tmp/iop-field-evidence")
def connected(name):
    d = json.loads((base / name).read_text())
    return sorted(n.get("node_id") for n in d.get("nodes", []) if n.get("connected"))
expected = ["gx10-vllm-node", "mac-codex-node", "onexplayer-lemonade-node"]
assert connected("reconnect-before-status.json") == expected, connected("reconnect-before-status.json")
assert connected("reconnect-after-status.json") == expected, connected("reconnect-after-status.json")
tcp = (base / "reconnect-after-tcp.txt").read_text()
assert tcp.count("TCP ") >= 3, tcp
p = (base / "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
assert "bootstrap_rerun=none" in (base / "reconnect-pids.txt").read_text()
rows = [json.loads(line) for line in (base / "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 reconnect_nodes=3 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-dev\n"
'
```

Expected: exit 0 and `FINAL_VERIFICATION=PASS field-reconnect field-refresh docs ...`.

## 수정 파일 요약

| 파일/영역 | 항목 |
|----------|------|
| `apps/node/internal/bootstrap/module.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-1 |
| `apps/node/internal/transport/session.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-1 |
| `apps/node/internal/transport/client.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-1 |
| `apps/node/internal/bootstrap/module_test.go` / `apps/node/internal/transport/*_test.go` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-1 |
| Remote `/tmp/iop-field-evidence/onexplayer-reconnect-diagnosis.txt` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-1 |
| Remote `/tmp/iop-field-evidence/reconnect-*.json`, `reconnect-*.txt`, `refresh-concurrent-4-rerun.jsonl` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-2 |
| `CODE_REVIEW-cloud-G08.md` | REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-1, REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-2 |

## 최종 검증

위 `REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_TEST-2`의 최종 검증 명령을 실행하고 실제 stdout/stderr를 `CODE_REVIEW-cloud-G08.md`에 붙여 넣는다.
