<!-- task=m-provider-resource-admission-ownership-alignment/12_node_connectivity_supervision plan=0 tag=REFACTOR -->

# Code Review Reference - REFACTOR

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill every implementation-owned section, keep active files in place, then end the implementation execution. The filled review-without-verdict state plus agent completion is the `review-ready` signal; do not create a handoff file/message, invoke code-review, or switch to the review role in place.
> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence, keep the active pair in place, and end the implementation execution; that file state is the same `review-ready` signal. Only the distinct review-agent instance decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves.
> Do not ask the user directly, present choices in chat, call `request_user_input`, report review readiness, or wait for another user instruction. The runtime immediately assigns a distinct review-agent instance; a new turn, resume, compaction, or role change of this implementing agent is not distinct.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, follow-up pair creation, `코드리뷰 전용 체크리스트`) is distinct-review-agent-only, even after compaction/resume. The implementing agent must never perform it.
> `USER_REVIEW.md` and `complete.log` are the only overall loop terminal states. `review-ready`, `implementation-ready`, a verdict, an archive, or a WARN/FAIL pair is internal transitional state and must not trigger a user-facing question or intermediate report.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-07-21
task=m-provider-resource-admission-ownership-alignment/12_node_connectivity_supervision, plan=0, tag=REFACTOR

## Roadmap Targets

- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-resource-admission-ownership-alignment.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/provider-resource-admission-ownership-alignment.md)
- Task ids:
  - `node-connectivity-supervision`: 최초 연결과 재연결을 동일 supervisor가 관리하고 `max_attempts=0` 무제한 정책을 보장
- Completion mode: check-on-pass

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

> **[HARD ACTOR BOUNDARY]** 리뷰 에이전트는 구현 에이전트와 별도 agent instance여야 한다. 새 turn·resume·compaction·역할 전환으로 같은 agent를 재사용할 수 없다. product source/test를 수정하거나 현재·후속 plan을 구현하거나 구현 에이전트 소유 섹션을 채우지 않는다. WARN/FAIL 후속 pair를 만든 뒤 review 실행을 종료하면 archived current pair와 fresh pair가 runtime의 `implementation-ready` 신호가 된다. 별도 handoff 파일·사용자 메시지를 만들지 않는다.
> **[FOLLOW-UP MANDATE]** 미해결 Required는 별도 후속 지시가 없어도 반드시 FAIL과 정확히 하나의 next state를 만든다. 기본 next state는 follow-up pair이고, 유효한 Milestone-lock gate만 그 pair 대신 `USER_REVIEW.md`를 만든다. 유효한 gate는 Required/FAIL로 분류한다. Suggested는 WARN follow-up을 만들고, Nit-only는 모든 차원이 Pass일 때만 PASS할 수 있다. reviewer는 후속 pair까지만 materialize하고 직접 구현하지 않는다.

> **[REVIEW AGENT ONLY]** 구현과 실제 출력을 대조한 뒤 `code_review_cloud_G08_0.log`, `plan_cloud_G08_0.log`로 rename한다. PASS에서만 `complete.log`와 task directory archive를 수행한다.

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| REFACTOR-1 Reconnect configuration contract and validation | [x] |
| REFACTOR-2 Typed dial failure classification | [x] |
| REFACTOR-3 Initial-connect supervisor and bounded lifecycle | [x] |

## 구현 체크리스트

- [x] config load가 reconnect 생략값 10, 양수 유한, 명시적 0 무제한을 구분하고 음수 및 무제한+비양수 interval을 거부한다.
- [x] 최초 연결 전에도 supervisor를 시작해 retryable Edge unavailable을 설정 정책으로 재시도하고 delayed startup 뒤 같은 Node process가 정확히 한 session을 수립한다.
- [x] dial/register 오류를 retryable/non-retryable로 분류해 config/credential/authoritative rejection은 즉시 exit 1, 유한 소진은 exit 1로 수렴한다.
- [x] 모든 상태에서 dial/session은 single-flight이며 shutdown이 sleep/dial을 취소하고 추가 attempt나 실패 exit 없이 정상 종료한다.
- [x] 생략/0/양수/음수, 10회 초과 성공, delayed startup, fatal rejection, exhaustion, shutdown과 `-race`를 deterministic test로 검증한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우고 active pair를 유지한 채 구현 실행을 종료한다. 이 저장 상태가 runtime의 review-ready 신호이며 별도 handoff 파일·사용자 메시지를 만들지 않는다. 구현 에이전트는 code-review를 실행하거나 review 전용 작업을 수행하지 않는다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. 구현 에이전트는 수정하거나 체크하지 않는다.

- [x] omitted/explicit 0 presence semantics와 invalid config validation을 loader output으로 대조한다.
- [x] typed retry classification이 원래 error/status를 보존하고 문자열 비교에 의존하지 않는지 확인한다.
- [x] initial/established state가 하나의 supervisor와 하나의 in-flight dial만 사용하는지 확인한다.
- [x] fatal/exhaust exit 1과 local shutdown 정상 종료가 경쟁 없이 정확히 한 번 수렴하는지 race evidence를 확인한다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나를 append하고 차원별 평가와 Required/Suggested/Nit를 일치시킨다.
- [x] active review를 `code_review_cloud_G08_0.log`, plan을 `plan_cloud_G08_0.log`로 rename한다.
- [x] `.gitignore` Agent-Ops block의 `agent-task/**/*.md`, `agent-task/**/*.log` unignore와 `agent-roadmap/current.md` ignore를 확인한다.
- [ ] PASS이면 complete-log template으로 `Roadmap Completion: node-connectivity-supervision`과 retry/delayed-start/race evidence를 기록하고 active `.md`를 남기지 않는다.
- [ ] PASS이면 task directory를 월별 archive로 이동하고 최종 `.log`에서 체크한다.
- [ ] PASS이면 runtime용 Milestone 완료 이벤트 메타데이터를 보고하고 roadmap/update-roadmap은 직접 호출하지 않는다.
- [ ] PASS split 이동 후 active parent에 sibling/file이 남아 있어 유지했는지 확인한다.
- [x] WARN/FAIL이고 user-review gate가 아니면 plan skill/fresh routing follow-up을 만들고 `complete.log`를 쓰지 않는다.
- [x] WARN/FAIL follow-up을 만든 뒤 review 실행을 종료한다. archived current pair와 fresh pair 및 reviewer 실행 종료가 runtime의 `implementation-ready` 신호다. 리뷰 에이전트는 후속 구현이나 별도 handoff artifact 생성을 수행하지 않으며 runtime이 별도 구현 에이전트 instance를 즉시 배정한다.
- [ ] USER_REVIEW이면 template으로 `USER_REVIEW.md`를 만들고 active pair와 `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW의 해소 결정이 완료/PASS이면 문서를 해소 상태로 갱신하고 `complete.log` 작성 및 archive 이동을 수행한다.
- [ ] USER_REVIEW의 해소 결정이 새 구현을 요구하면 plan이 USER_REVIEW를 archive하고 fresh pair를 쓴 뒤 review 실행을 종료한다. 이 상태가 `implementation-ready`이며 runtime이 별도 구현-agent instance를 배정한다.

## 계획 대비 변경 사항

- REFACTOR-1: `packages/go/config/node_types.go`의 `ReconnectConf` 필드에 계약 주석을 추가하고, `packages/go/config/load.go`의 `Load`에 `validateReconnect`를 추가했다. `setDefaults`는 그대로 생략값 10/10을 공급하고, validation은 `max_attempts<0`, `interval_sec<0`, `max_attempts==0 && interval_sec<=0`을 거부한다. `node_config_test.go`에 `TestLoadReconnectPolicy`(omitted=10, explicit 0=unlimited, positive finite, finite+0 interval)와 `TestLoadRejectsInvalidReconnectPolicy`(negative max, negative interval, unlimited+0, unlimited+negative)를 table test로 추가했다. 기존 `TestLoad_NodeReconnectDefaults`/`Override`는 계약 유지 근거로 그대로 뒀다.
- REFACTOR-2: `apps/node/internal/transport/client.go`에 `ConnectError`(class+cause, `Error/Unwrap/Retryable`), 내부 `connectFailureRetryable/Fatal`, `wrapConnectError`, 그리고 bootstrap이 소비할 `IsFatalConnectError`를 추가했다. `DialEdge`의 4개 실패 지점을 wrapping했다(주소/port 준비=fatal, dial/Edge unavailable=retryable, register transport 실패=retryable, authoritative rejection=fatal). 원인은 모두 `%w`로 보존해 `errors.Is/As`와 transport status가 유지된다. `client_test.go`에 white-box `TestDialEdgeClassifiesConnectFailures`(invalid addr/port=fatal, connection refused=retryable+`*net.OpError` 보존, rejected registration=fatal+reason 보존, unclassified=retryable)를 추가했다.
- REFACTOR-3: `apps/node/internal/bootstrap/runtime_supervisor.go`를 단일 supervisor state machine으로 재작성했다. `start`는 metrics 서버와 supervisor goroutine만 띄우고 즉시 반환한다(OnStart non-blocking). `run`이 initial connect→session Done 대기→reconnect를 직렬로 소유하고, 공통 `connect(initial)`이 두 경로를 처리한다. fatal은 `transport.IsFatalConnectError`로만 판정하며 문자열 비교를 하지 않는다. `stop`은 supervisor cancel 후 goroutine done을 대기해 shutdown 이후 추가 attempt가 없고 정상 종료함을 보장한다. `module_test.go`에 신규 5개(`TestSupervisorRetriesInitialDialUntilDelayedSuccess`, `...UnlimitedAttemptsPassesTen`, `...FiniteExhaustionExitsOne`, `...FatalFailureDoesNotRetry`, `...ShutdownCancelsRetry`)와 재사용 helper(`startAcceptingEdge`, `startRejectingEdge`)를 추가했다.
- `apps/node/internal/bootstrap/module.go`는 기능적 변경이 없다. `start`가 non-blocking이 되면서 기존 OnStart/OnStop wiring과 `connectRuntime`이 그대로 올바르게 동작하므로 추가 wiring 조정이 불필요했다.
- 기존 테스트 조정(계약 불변): `TestReconnectSupervisorReestablishesSession`/`ExhaustsRetries`/`PolicyTimingAndLimit`는 old blocking-start를 전제로 초기 등록 완료 전에 `firstClient`를 닫아, non-blocking start에서는 초기 registration과 close가 경쟁해 초기 연결이 실패하는 race가 있었다. 각 테스트가 "초기 session 수립(established)"을 기다린 뒤 disconnect를 유도하도록 조정했다. dial/sleep count 기대값(1 initial + N reconnect, 10 sleeps 등)과 exit-code 계약은 그대로 유지했다.

## 주요 설계 결정

- Presence semantics: viper `setDefaults`가 생략 `max_attempts`를 10으로 공급하고 explicit 0은 보존한다. validation은 load 시점에만 수행하고, runtime은 과거의 `maxAttempts<=0→10`, `intervalSec<0→10` 재보정을 제거해 explicit 0(unlimited) 의미를 파괴하지 않는다. unlimited는 `max_attempts==0`이며 positive interval을 강제한다.
- Retry classification: `DialEdge`만 `ConnectError`를 생성한다. 주소/port 준비 오류와 authoritative registration rejection은 fatal, dial/register transport 실패는 retryable로 표식한다. 미분류(비-`ConnectError`) 오류는 retryable 기본값이라 fake dialer/post-dial `connectRuntime` 실패는 재시도로 수렴하며 이는 기존 reconnect 동작과 일치한다. bootstrap은 `IsFatalConnectError`만 소비하고 `errors.As`로 원인을 unwrap한다.
- Attempt counting: `connect(initial)`은 initial일 때 첫 시도를 즉시(sleep 없이) 수행하고 이후 시도와 모든 reconnect 시도 전에는 interval sleep을 넣는다. finite 정책은 정확히 `max_attempts`회 시도 후 exhaust하고, unlimited(0)는 cancel까지 지속한다. finite 마지막 시도에서 local shutdown이 겹치면 루프 종료 후 `supCtx.Err()`를 확인해 exhaust(exit 1) 대신 정상 종료한다.
- Fatal/exhaust/cancel lifecycle: fatal과 finite exhaustion은 각각 `clearCurrent` 후 `Shutdown(ExitCode(1))`로 수렴한다. single-flight는 단일 supervisor goroutine이 dial→session→reconnect를 직렬 소유해 동시 dial과 session이 각각 최대 1개임을 구조적으로 보장한다. local shutdown은 `stop`이 `cancelSupervisor()`→`<-done` 대기→`clearCurrent` 순서로 처리해 대기 중 sleep/dial을 취소하고 추가 attempt·실패 exit 없이 종료한다. established-session close signal(`session.go`의 disconnect listener/`Done()`/`IsLocalShutdown()`)과 wire/proto는 변경하지 않았다.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 채운 뒤 구현 에이전트는 code-review를 실행하거나 역할을 바꾸지 않고 active pair를 유지한 채 구현 실행을 종료한다. 이 저장 상태가 runtime의 `review-ready` 신호이므로 별도 handoff 파일·사용자 메시지를 만들지 않는다. runtime은 사용자에게 묻지 않고 별도 review 에이전트 instance를 즉시 배정하며, review 에이전트가 `USER_REVIEW.md`를 실제로 만들기 전에는 terminal 상태가 아니다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 blocker evidence·결정적 해제 조건·재검증 명령을 `검증 결과`나 `계획 대비 변경 사항`에 기록한 뒤 code-review의 일반 follow-up plan으로 처리한다._

- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음

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

- 생략값 10과 명시적 0이 loader/runtime 양쪽에서 혼동되지 않는가.
- unlimited loop는 positive interval을 강제하고 shutdown context에 즉시 반응하는가.
- initial failure가 Fx startup을 끝내지 않고 supervisor 정책으로 들어가며 non-retryable 오류는 반복되지 않는가.
- finite attempt 수, exit code, concurrent dial 최대값, shutdown 후 attempt 수가 deterministic assertion으로 고정됐는가.
- delayed Edge startup 뒤 process 재시작 없이 정확히 한 active session이 수립되는가.

## 검증 결과

아래 각 명령의 실제 stdout/stderr를 붙인다. 명령 변경은 `계획 대비 변경 사항`에 이유를 기록한다.

### 중간 검증

`go test -count=1 ./packages/go/config -run 'Reconnect'`

`go test -count=1 ./apps/node/internal/transport ./apps/node/internal/bootstrap -run 'Dial|Retry|Fatal'`

`go test -race -count=1 ./apps/node/internal/bootstrap -run 'Supervisor|Initial|Reconnect|Shutdown'`

```
ok  	iop/packages/go/config	0.088s
ok  	iop/apps/node/internal/transport	0.128s
ok  	iop/apps/node/internal/bootstrap	0.592s
ok  	iop/apps/node/internal/bootstrap	2.400s   (-race)
```

### gofmt

`gofmt -d packages/go/config/node_types.go packages/go/config/load.go packages/go/config/node_config_test.go apps/node/internal/transport/client.go apps/node/internal/transport/client_test.go apps/node/internal/bootstrap/runtime_supervisor.go apps/node/internal/bootstrap/module.go apps/node/internal/bootstrap/module_test.go`

```
(출력 없음 — exit 0, 전 파일 clean)
```

### config와 대상 package

`go test -count=1 ./packages/go/config -run 'Reconnect'`

`go test -count=1 ./apps/node/internal/transport ./apps/node/internal/bootstrap`

```
ok  	iop/apps/node/internal/transport	5.697s
ok  	iop/apps/node/internal/bootstrap	1.194s
```

### race와 회귀

`go test -race -count=1 ./apps/node/internal/transport ./apps/node/internal/bootstrap`

`go test -count=1 ./apps/node/...`

`go test -count=1 ./packages/go/...`

`go test -count=1 ./...`

```
# -race transport+bootstrap
ok  	iop/apps/node/internal/transport	6.779s
ok  	iop/apps/node/internal/bootstrap	2.457s
# 반복 안정성: bootstrap -race -count=3 ok(6.129s), transport -race -count=2 ok(12.546s)

# go test ./apps/node/...  → 변경 범위 전부 PASS. 유일 실패는 adapters/cli.
# go test ./packages/go/... → 전부 PASS (audit/config/hostsetup/observability ok)
# go test ./...             → 33 packages ok, 유일 실패 adapters/cli.

# [환경 blocker] apps/node/internal/adapters/cli 실패는 이 변경과 무관하다:
#  - cli 패키지는 transport/bootstrap를 import하지 않고 config.Load를 호출하지 않는다.
#  - 실패 테스트 집합이 실행마다 비결정적으로 바뀐다(예: 1회차 6건 중 1건은 재실행 시 PASS,
#    ./... 실행에서는 다른 조합). 오류가 실제 `sh`/TUI subprocess spawn 타이밍
#    ("start \"sh\": context deadline exceeded", "context canceled")이라 sandbox의 느린
#    프로세스 생성에 기인한다. code-review에서 재확인 가능한 재검증 명령:
#    go test -count=1 ./apps/node/internal/adapters/cli
```

### smoke

`IOP_E2E_PROFILE=mock ./scripts/e2e-smoke.sh`

```
[e2e] Auxiliary smoke test PASSED.
[e2e] Completion still requires scripts/dev/edge.sh + scripts/dev/node.sh user-flow verification.
```

- full-cycle smoke가 새 supervisor로 PASS: OnStart hook이 4.333µs에 반환(non-blocking startup 확인), 이어서
  supervisor goroutine이 `"connecting to edge","initial":true,"attempt":1,"max_attempts":10,"unlimited":false,"interval_sec":10`
  로그 후 `registered with edge`→`connected to edge`, 메시지 2회 왕복, `/capabilities`·`/transport`·`/sessions`·
  terminate-session, 정상 disconnect+OnStop까지 통과했다.
- 참고 blocker: `scripts/dev/edge.sh`가 `go run`으로 edge를 cold-compile하므로 build cache가 차갑고 sandbox가 느리면
  smoke의 10s edge-bind deadline을 초과해 false "edge failed to bind"가 났다(edge는 실제로 `IOP Edge console listening on 127.0.0.1:<port>`로 바인딩됨).
  edge/node 바이너리를 미리 warm(`go build ./apps/edge/cmd/edge ./apps/node/cmd/node`)한 뒤 재실행하면 위처럼 PASS한다. 코드 결함이 아니라 환경/타이밍 blocker다.

### delayed initial-connect / S16·S17 deterministic evidence

- S16(delayed initial startup, 정확히 한 session): `TestSupervisorRetriesInitialDialUntilDelayedSuccess` — 실 TCP mini-edge에 초기 dial을 재시도(3 실패→성공), dial=4, 재등록 없음(single session) PASS.
- S17(unlimited 10회 초과): `TestSupervisorUnlimitedAttemptsPassesTen` — `max_attempts=0`으로 15회 실패 뒤 16번째 성공, 10회 cap 없음 PASS.
- S17(finite exhaustion exit 1): `TestSupervisorFiniteExhaustionExitsOne` — `max_attempts=3` 정확히 3회 후 exit 1 PASS.
- S17(non-retryable fatal exit 1, no retry): `TestSupervisorFatalFailureDoesNotRetry` — rejected registration 1회 dial 후 exit 1 PASS.
- S17(local shutdown cancel, no-extra-attempt, clean): `TestSupervisorShutdownCancelsRetry` — retry sleep 중 shutdown이 sleep 취소, 추가 dial 없음, 정상 종료 PASS.
- S17(config validation): `TestLoadReconnectPolicy`/`TestLoadRejectsInvalidReconnectPolicy` PASS.
- reconnect 회귀: `TestReconnectSupervisorReestablishesSession`/`ExhaustsRetries`/`PolicyTimingAndLimit`(1 initial + N reconnect, 10 sleeps, exit 1) PASS. 전부 `-race -count=3`에서도 안정.

---

> **[IMPLEMENTING AGENT — BEFORE SAVING]** completion table, checklist, 변경 사항, 설계 결정, 검증 stdout/stderr를 모두 채우고 active 파일을 유지한 채 구현 실행을 종료한다. 이 파일 상태와 실행 종료가 runtime의 `review-ready` 신호이므로 별도 handoff 파일·사용자 메시지를 만들지 않는다. code-review 실행, 역할 전환, 판정, review 전용 섹션 수정, archive/terminal/follow-up 처리는 금지한다.

## 섹션 소유권

| Section | Owner | Note |
|---------|-------|------|
| Header/개요/Roadmap Targets/checkpoints | Fixed | 수정 금지 |
| 구현 표/체크리스트/변경/설계/검증 | Implementing agent | 실제 내용 필수 |
| 리뷰 체크리스트/결과 | Review agent | 구현 에이전트 수정 금지 |
| 사용자 리뷰 요청 | Implementing agent | Milestone 잠금 결정만 |

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가:
  - correctness: Fail
  - completeness: Fail
  - test coverage: Fail
  - API contract: Fail
  - code quality: Pass
  - implementation deviation: Fail
  - verification trust: Fail
  - spec conformance: Fail
- 발견된 문제:
  - Required — `apps/node/internal/bootstrap/runtime_supervisor.go:122`: dial이 돌는 동안 local shutdown이 발생해도 dial 반환 후 `supCtx.Err()`를 다시 확인하기 전에 success owner를 설치하거나 fatal 오류로 exit 1을 요청할 수 있다. dial 반환 직후 cancellation을 우선 판정하고, 동시에 반환된 owner는 닫은 뒤 정상 종료하도록 고친다. `apps/node/internal/bootstrap/module_test.go`에 context cancel을 관찰한 뒤 fatal/success를 반환하는 in-flight dial 경쟁 테스트를 추가해 exit 1과 추가 attempt가 없음을 고정한다.
  - Required — `apps/node/internal/transport/client.go:117`: `strconv.Atoi` 후 port 범위를 검증하지 않아 `0`, 음수, `65536` 이상의 local config 오류가 `DialContext` 오류로 넘어가 retryable로 분류된다. unlimited profile에서는 이로 인해 회복 불가능한 설정으로 영구 재시도하므로 S17의 non-retryable local config 계약을 어긴다. 유효한 TCP port 범위를 dial 전에 fatal `ConnectError`로 검증하고 `apps/node/internal/transport/client_test.go`에 경계값을 추가한다.
  - Required — `apps/node/internal/bootstrap/module_test.go:114`: `TestSupervisorRetriesInitialDialUntilDelayedSuccess` 는 Edge server를 Node보다 먼저 시작하고 fake dialer로 오류만 주입하므로 S16 Evidence Map의 delayed-network/Edge startup을 실제 `DialEdge` 경로로 검증하지 않는다. 또한 fresh auxiliary smoke는 warm build 후에도 10초 Edge bind deadline에서 실패해 기록된 full-cycle 요약을 독립적으로 재현하지 못했다. 같은 Node app을 먼저 시작한 뒤 실제 closed port 실패를 여러 번 관찰하고 동일 port에 Edge를 늦게 열어 단일 registration/session으로 수렴하는 deterministic integration test를 추가하며, `scripts/dev/edge.sh` + `scripts/dev/node.sh` full-cycle을 재현 가능한 stdout/stderr로 다시 기록한다.
- 다음 단계: FAIL finding만을 대상으로 `plan`/`finalize-task-routing` 재평가를 수행한 뒤 fresh follow-up pair를 생성한다.
