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

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

## 배경

현재 Milestone의 reconnect, refresh, node-config 구현 Task는 완료됐고 field Epic만 남았다. SDD S16/S17/S18은 dev-runtime에서 Edge restart, refresh apply, 문서/agent-test 기준까지 실제 field evidence로 닫으라고 요구한다. 문서와 local field 기준은 이번 요청의 작은 작업으로 먼저 갱신됐으므로, 이 plan은 원격 field 검증과 그 문서 기준의 최종 정합성 확인을 수행한다.

## 사용자 리뷰 요청 흐름

선택된 Milestone lock 결정만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 중 직접 사용자에게 묻지 말고, code-review가 해당 요청을 검증한 뒤 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## 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-roadmap/current.md`
- `agent-roadmap/phase/update-plane-self-update-foundation/PHASE.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-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/node-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-test/local/testing-smoke.md`
- `apps/edge/README.md`
- `docs/edge-local-dev-guide.md`
- `apps/edge/internal/edgecmd/config.go`
- `configs/edge.yaml`
- `apps/edge/cmd/edge/edge.yaml`
- `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`: Edge restart 후 bootstrap 재실행 없이 mac-codex, GX10, OneXPlayer Node 3개 연결 회복.
  - S17 -> `field-refresh`: `gx10-vllm=4`, `onexplayer-lemonade=3` refresh apply 후 Edge restart 없이 `qwen3.6:35b` 동시 4개 요청 성공.
  - S18 -> `docs`: docs diff와 agent-test field rule diff.
- Evidence Map:
  - S16 evidence: remote dev field status와 TCP connection evidence.
  - S17 evidence: remote dev 4 concurrent OpenAI-compatible requests와 refresh result evidence.
  - S18 evidence: docs diff와 agent-test field rule diff.
- Checklist와 final verification은 위 세 evidence row에서 역산했다. refresh 결과 JSON, Edge PID 유지, `/v1/models`, 동시 요청 출력, Control Plane status, TCP connection count를 review stub에 붙여 넣어야 한다.

### 테스트 환경 규칙

- `test_env=local`.
- `agent-test/local/rules.md`를 읽었다. field/bootstrap, 외부 runtime evidence는 원격 runner `ssh toki@toki-labs.com`의 `/Users/toki/agent-work/iop-dev` 기준이다.
- 읽은 matched profiles: `agent-test/local/edge-smoke.md`, `agent-test/local/node-smoke.md`, `agent-test/local/platform-common-smoke.md`, `agent-test/local/testing-smoke.md`.
- 적용 명령/기준: 원격 dev-runtime `build/dev-runtime/edge.yaml`, 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`, model alias `qwen3.6:35b`, capacity `gx10-vllm=4`, `onexplayer-lemonade=3`.
- `agent-test/local/*`는 `.gitignore`의 `agent-test/local/` 규칙으로 ignore된다. 내용은 local 기준으로 갱신됐지만 tracked diff에는 나타나지 않는다. review는 파일 내용을 직접 확인한다.
- `<확인 필요>` 값 없음. refresh admin address는 `build/dev-runtime/edge.yaml`의 `refresh.listen`을 우선하고, 없으면 `127.0.0.1:19093`를 사용한다.

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

- `field-reconnect`: 기존 unit/integration은 reconnect 동작을 닫았지만 GX10 Linux, OneXPlayer Windows, mac-codex dev-runtime 3 Node 회복 evidence는 없다. 이 plan에서 원격 field evidence로 닫는다.
- `field-refresh`: 기존 unit/integration은 refresh apply, Node push, adapter diff, drain을 닫았지만 실제 provider pool capacity 4/3과 동시 4개 요청 evidence는 없다. 이 plan에서 원격 field evidence로 닫는다.
- `docs`: 작은 작업으로 docs/agent-test 기준을 갱신했다. 이 plan에서 diff와 실제 field 결과가 어긋나지 않는지 확인한다.

### 심볼 참조

- none. 이 plan은 symbol rename/remove를 하지 않는다.

### 분할 판단

- split decision policy를 먼저 평가했다. 원격 field 검증은 외부 runner, multi-host Node, provider runtime, OpenAI-compatible request evidence가 중심이어서 local 단일 작업으로 닫기 어렵다.
- 공유 task group: `m-runtime-reconnect-config-refresh`.
- 선택 subtask: `07+01,03,04,05,06_field_docs_smoke`.
- dependency source of truth: directory name. Direct predecessors:
  - `01`: satisfied by `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/01_reconnect_supervisor/complete.log`.
  - `03`: satisfied by `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/03+02_edge_refresh_apply/complete.log`.
  - `04`: satisfied by `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/04+02_config_push_contract/complete.log`.
  - `05`: satisfied by `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/05+04_node_adapter_diff/complete.log`.
  - `06`: satisfied by `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/06+05_node_drain_runtime_config/complete.log`.
- `02_refresh_contract` is not a direct predecessor in this subtask name; it is indirectly covered by `03+02`.

### 범위 결정 근거

- 구현 코드를 바꾸지 않는다. Field evidence만으로 닫는 작업이며, code defect가 나오면 이 plan을 억지로 확장하지 말고 review 결과에서 follow-up plan을 만든다.
- token, API key, private endpoint 원문은 tracked docs, roadmap, review output에 기록하지 않는다. 필요한 경우 마스킹한다.
- 정확한 per-request `node_id` 배정은 현재 공개 API가 노출하지 않으므로 dispatch trace가 이미 있지 않으면 aggregate smoke로 판정한다.
- `agent-task/archive/**`는 더 읽지 않는다. 추가 prior context가 필요하면 위 direct predecessor `complete.log` 경로만 사용한다.

### 빌드 등급

- `cloud-G08`: 실제 원격 runner, process restart, provider pool runtime, multi-host Node reconnect, concurrent OpenAI-compatible 요청 evidence가 중심이라 local bounded verification 조건을 만족하지 않는다.

## 구현 체크리스트

- [ ] `TEST-1` 원격 dev-runtime에서 Edge restart 후 bootstrap 재실행 없이 3개 Node가 회복되는 evidence를 수집한다. 검증: Control Plane status와 Edge node TCP connection 수가 3개 Node 기준으로 회복된다.
- [ ] `TEST-2` `gx10-vllm=4`, `onexplayer-lemonade=3` capacity를 refresh apply로 반영하고 Edge PID 유지와 `qwen3.6:35b` 동시 4개 요청 성공 evidence를 수집한다. 검증: aggregate 기대 배정은 capacity 4/3 기준 `gx10-vllm` 2개, `onexplayer-lemonade` 2개이며, 요청별 node id 확정은 dispatch trace가 구현된 경우에만 판정한다.
- [ ] `TEST-3` docs와 agent-test field 기준이 실제 field 결과와 일치하는지 확인하고, 필요한 경우 문서만 보정한다.
- [ ] 최종 검증 명령을 모두 실행하고 실제 stdout/stderr를 `CODE_REVIEW-cloud-G08.md`에 붙여 넣는다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

`07+01,03,04,05,06_field_docs_smoke`는 directory name 기준으로 `01`, `03`, `04`, `05`, `06`의 `complete.log`가 선행 조건이다. 모두 archive에서 확인됐다. 구현 순서는 `TEST-1` reconnect baseline, `TEST-2` refresh/concurrency, `TEST-3` docs consistency 순서로 진행한다.

### [TEST-1] Field Reconnect Evidence

문제: Milestone `field-reconnect`는 [runtime-reconnect-config-refresh.md](/config/workspace/iop/agent-roadmap/phase/update-plane-self-update-foundation/milestones/runtime-reconnect-config-refresh.md:78)에서 Edge restart 후 GX10 Linux Node와 OneXPlayer Windows Node가 bootstrap 재실행 없이 재접속해야 한다고 고정한다. SDD S16은 [SDD.md](/config/workspace/iop/agent-roadmap/sdd/update-plane-self-update-foundation/runtime-reconnect-config-refresh/SDD.md:102)에서 mac-codex까지 포함한 3 Node 회복 evidence를 요구한다. Current docs/test 기준은 [edge-local-dev-guide.md](/config/workspace/iop/docs/edge-local-dev-guide.md:132), [edge-smoke.md](/config/workspace/iop/agent-test/local/edge-smoke.md:145)에 맞춰져 있다.

해결 방법: 원격 runner에서 현재 Edge PID, Control Plane status, TCP established count를 저장한다. Edge process를 재시작하되 Node bootstrap 명령은 재실행하지 않는다. retry window 안에서 Control Plane status와 TCP count가 3 Node 기준으로 회복되는지 기록한다.

수정 파일 및 체크리스트:

- [ ] 코드 파일 수정 없음.
- [ ] `CODE_REVIEW-cloud-G08.md`에 before/after Control Plane status JSON 또는 요약을 붙인다.
- [ ] `CODE_REVIEW-cloud-G08.md`에 before/after TCP connection count와 Edge PID change를 붙인다.
- [ ] bootstrap 명령 재실행 여부를 명시한다. 기대값은 재실행 없음이다.

테스트 작성: 새 automated test는 작성하지 않는다. 이 Task는 SDD Evidence Map상 remote dev field evidence가 요구되는 field smoke다.

중간 검증:

```bash
ssh toki@toki-labs.com 'set -euo pipefail
cd /Users/toki/agent-work/iop-dev
mkdir -p /tmp/iop-field-evidence
EDGE_CONFIG=build/dev-runtime/edge.yaml
EDGE_BIN="$(command -v iop-edge || true)"
if [ -z "$EDGE_BIN" ] && [ -x ./build/bin/iop-edge ]; then EDGE_BIN=./build/bin/iop-edge; fi
if [ -z "$EDGE_BIN" ] && [ -x ./build/dev-runtime/iop-edge ]; then EDGE_BIN=./build/dev-runtime/iop-edge; fi
test -n "$EDGE_BIN"
BEFORE_PID="$(pgrep -f "iop-edge.*${EDGE_CONFIG}" | head -n 1)"
test -n "$BEFORE_PID"
curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs/status | tee /tmp/iop-field-evidence/reconnect-before-status.json
(lsof -nP -iTCP:18084 -sTCP:ESTABLISHED 2>/dev/null || ss -tn state established "( sport = :18084 )" 2>/dev/null || true) | tee /tmp/iop-field-evidence/reconnect-before-tcp.txt
kill "$BEFORE_PID"
for i in $(seq 1 20); do if ! kill -0 "$BEFORE_PID" 2>/dev/null; then break; fi; sleep 1; done
nohup "$EDGE_BIN" --config "$EDGE_CONFIG" serve > /tmp/iop-field-evidence/reconnect-edge-restart.stdout.log 2>&1 &
AFTER_PID="$!"
echo "before_pid=$BEFORE_PID after_pid=$AFTER_PID"
for i in $(seq 1 120); do
  curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs/status > /tmp/iop-field-evidence/reconnect-after-status.json && break || true
  sleep 1
done
(lsof -nP -iTCP:18084 -sTCP:ESTABLISHED 2>/dev/null || ss -tn state established "( sport = :18084 )" 2>/dev/null || true) | tee /tmp/iop-field-evidence/reconnect-after-tcp.txt
cat /tmp/iop-field-evidence/reconnect-after-status.json
'
```

Expected: command exits 0, status contains `mac-codex-node`, `gx10-vllm-node`, `onexplayer-lemonade-node` connected or equivalent connected-node count 3, TCP established count is 3, and no Node bootstrap command was rerun.

### [TEST-2] Field Refresh And Concurrent Requests

문제: Milestone `field-refresh`는 [runtime-reconnect-config-refresh.md](/config/workspace/iop/agent-roadmap/phase/update-plane-self-update-foundation/milestones/runtime-reconnect-config-refresh.md:79)에서 capacity `gx10-vllm=4`, `onexplayer-lemonade=3`를 refresh로 반영하고 Edge restart 없이 동시 4개 요청이 성공해야 한다고 요구한다. SDD S17은 [SDD.md](/config/workspace/iop/agent-roadmap/sdd/update-plane-self-update-foundation/runtime-reconnect-config-refresh/SDD.md:103)에서 refresh result evidence와 remote dev 4 concurrent OpenAI-compatible request evidence를 요구한다.

해결 방법: 원격 runner에서 `build/dev-runtime/edge.yaml`의 capacity와 refresh listen을 확인한다. `config check`, `config refresh --mode dry-run`, `config refresh --mode apply`를 실행한다. apply 전후 Edge PID가 같은지 확인한다. `/healthz`, `/v1/models`, `qwen3.6:35b` 동시 4개 `/v1/chat/completions`를 실행하고 response success, usage, 가능하면 dispatch trace를 저장한다.

수정 파일 및 체크리스트:

- [ ] 코드 파일 수정 없음.
- [ ] refresh dry-run JSON과 apply JSON을 `CODE_REVIEW-cloud-G08.md`에 붙인다.
- [ ] Edge PID before/after를 붙여 Edge restart 없이 refresh가 적용됐음을 증명한다.
- [ ] 동시 4개 요청 raw output 또는 saved output path를 붙인다.
- [ ] dispatch trace가 있으면 provider별 aggregate 2/2를 확인하고, 없으면 공개 API 한계와 aggregate smoke 판정 근거를 기록한다.

테스트 작성: 새 automated test는 작성하지 않는다. 이 Task는 실제 provider pool field smoke다.

중간 검증:

```bash
ssh toki@toki-labs.com 'set -euo pipefail
cd /Users/toki/agent-work/iop-dev
mkdir -p /tmp/iop-field-evidence
EDGE_CONFIG=build/dev-runtime/edge.yaml
EDGE_BIN="$(command -v iop-edge || true)"
if [ -z "$EDGE_BIN" ] && [ -x ./build/bin/iop-edge ]; then EDGE_BIN=./build/bin/iop-edge; fi
if [ -z "$EDGE_BIN" ] && [ -x ./build/dev-runtime/iop-edge ]; then EDGE_BIN=./build/dev-runtime/iop-edge; fi
test -n "$EDGE_BIN"
REFRESH_ADDR="$(python3 - <<'"'"'PY'"'"'
from pathlib import Path
import re
p=Path("build/dev-runtime/edge.yaml")
s=p.read_text()
m=re.search(r"(?ms)^refresh:\n(?:^[ \t]+.*\n)*?^[ \t]+listen:[ \t]*\"?([^\"\n]+)\"?", s)
print(m.group(1).strip() if m else "127.0.0.1:19093")
PY
)"
EDGE_PID_BEFORE="$(pgrep -f "iop-edge.*${EDGE_CONFIG}" | head -n 1)"
test -n "$EDGE_PID_BEFORE"
"$EDGE_BIN" --config "$EDGE_CONFIG" config check | tee /tmp/iop-field-evidence/refresh-config-check.txt
"$EDGE_BIN" --config "$EDGE_CONFIG" config refresh --mode dry-run --addr "$REFRESH_ADDR" | tee /tmp/iop-field-evidence/refresh-dry-run.json
"$EDGE_BIN" --config "$EDGE_CONFIG" config refresh --mode apply --addr "$REFRESH_ADDR" | tee /tmp/iop-field-evidence/refresh-apply.json
EDGE_PID_AFTER="$(pgrep -f "iop-edge.*${EDGE_CONFIG}" | head -n 1)"
printf "edge_pid_before=%s edge_pid_after=%s\n" "$EDGE_PID_BEFORE" "$EDGE_PID_AFTER" | tee /tmp/iop-field-evidence/refresh-pid.txt
test "$EDGE_PID_BEFORE" = "$EDGE_PID_AFTER"
curl -fsS http://toki-labs.com:18083/healthz | tee /tmp/iop-field-evidence/refresh-healthz.txt
curl -fsS http://toki-labs.com:18083/v1/models | tee /tmp/iop-field-evidence/refresh-models.json
python3 - <<'"'"'PY'"'"' | tee /tmp/iop-field-evidence/refresh-concurrent-4.jsonl
import concurrent.futures, json, urllib.request
url = "http://toki-labs.com:18083/v1/chat/completions"
payload = {
  "model": "qwen3.6:35b",
  "messages": [{"role": "user", "content": "Reply with exactly IOP_FIELD_REFRESH_OK."}],
  "max_tokens": 32,
  "temperature": 0
}
def one(i):
    data = json.dumps(payload).encode()
    req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=300) as resp:
        body = json.loads(resp.read().decode())
    return {"index": i, "status": "ok", "id": body.get("id"), "model": body.get("model"), "usage": body.get("usage"), "content": body.get("choices", [{}])[0].get("message", {}).get("content", "")}
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as ex:
    for item in ex.map(one, range(4)):
        print(json.dumps(item, ensure_ascii=False))
PY
'
```

Expected: command exits 0, refresh apply status is `applied` or no-op applied with relevant changed items already current, Edge PID before/after is identical, `/healthz` and `/v1/models` succeed, four concurrent responses succeed for `qwen3.6:35b`.

### [TEST-3] Docs And Field Rule Consistency

문제: SDD S18 requires docs and field smoke criteria to reflect reconnect/config refresh behavior. The small immediate patch updated [apps/edge/README.md](/config/workspace/iop/apps/edge/README.md:118), [docs/edge-local-dev-guide.md](/config/workspace/iop/docs/edge-local-dev-guide.md:120), [edge-smoke.md](/config/workspace/iop/agent-test/local/edge-smoke.md:136), and [rules.md](/config/workspace/iop/agent-test/local/rules.md:52). The implementation pass must ensure field evidence does not contradict those docs.

해결 방법: Run deterministic text checks for stale pre-refresh wording and compare docs with actual field evidence. If the field run proves a different operational limit, update only the relevant docs/test-rule text.

수정 파일 및 체크리스트:

- [ ] `apps/edge/README.md` stays aligned with actual `iop-edge config refresh` command and reconnect policy.
- [ ] `docs/edge-local-dev-guide.md` stays aligned with dev-runtime ports and refresh/reconnect sequence.
- [ ] `agent-test/local/rules.md` and `agent-test/local/edge-smoke.md` stay aligned with field evidence. These files are ignored by git but still part of local agent-test criteria.

테스트 작성: no automated test. Use deterministic text checks plus remote field evidence.

중간 검증:

```bash
git diff -- apps/edge/README.md docs/edge-local-dev-guide.md
! rg --sort path -n "hot reload되지 않는다|자동 재등록 루프를 보장하지 않으므로|Edge process restart 후 새 capacity" apps/edge/README.md docs/edge-local-dev-guide.md agent-test/local/rules.md agent-test/local/edge-smoke.md
```

Expected: tracked docs diff only contains reconnect/refresh docs changes, and stale wording search has no matches.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/README.md` | TEST-3 |
| `docs/edge-local-dev-guide.md` | TEST-3 |
| `agent-test/local/rules.md` | TEST-3 |
| `agent-test/local/edge-smoke.md` | TEST-3 |
| `agent-task/m-runtime-reconnect-config-refresh/07+01,03,04,05,06_field_docs_smoke/CODE_REVIEW-cloud-G08.md` | TEST-1, TEST-2, TEST-3 |

## 최종 검증

```bash
git diff --check
```

Expected: no output.

```bash
! rg --sort path -n "hot reload되지 않는다|자동 재등록 루프를 보장하지 않으므로|Edge process restart 후 새 capacity" apps/edge/README.md docs/edge-local-dev-guide.md agent-test/local/rules.md agent-test/local/edge-smoke.md
```

Expected: no matches.

```bash
ssh toki@toki-labs.com 'test -s /tmp/iop-field-evidence/reconnect-after-status.json && test -s /tmp/iop-field-evidence/reconnect-after-tcp.txt && test -s /tmp/iop-field-evidence/refresh-apply.json && test -s /tmp/iop-field-evidence/refresh-concurrent-4.jsonl && wc -l /tmp/iop-field-evidence/refresh-concurrent-4.jsonl'
```

Expected: command exits 0 and `wc -l` reports `4`.

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