<!-- task=m-provider-resource-admission-ownership-alignment/11+08,13,15_reconnect_candidate_recovery plan=0 tag=REFACTOR -->

# Accepted reconnect candidate recovery

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

08, 13, 15 predecessor의 matching active/archive `complete.log`를 모두 확인하고 구현한다. 구현 에이전트는 코드·테스트 변경, 구현 검증, active review stub의 구현 에이전트 소유 섹션 작성까지만 수행한다. 모두 채운 뒤 active pair를 유지하고 구현 실행을 종료한다. 이 저장 상태가 runtime의 `review-ready` 신호이며 별도 handoff 파일·사용자 메시지를 만들지 않는다. code-review 실행, 역할 전환, 판정, review 전용 섹션 수정, archive/log rename, `USER_REVIEW.md`·`complete.log` 작성, 후속 pair 생성·구현은 금지한다. runtime은 사용자에게 묻거나 준비 완료를 보고하지 않고 별도 review-agent instance를 즉시 배정하며 같은 agent의 새 turn·resume·compaction으로 대체하지 않는다. 선택 Milestone의 `구현 잠금 > 결정 필요`만 linked evidence와 함께 `사용자 리뷰 요청`에 기록하고 같은 file-state handoff를 사용한다. 환경·secret·서비스·일반 scope 변경·follow-up으로 닫을 수 있는 evidence gap은 user-review 사유가 아니다.

## 배경

disconnect 시 resource를 orphan 처리하고 queue candidate를 제거하지만 accepted reconnect에는 service/queue direct lifecycle hook이 없다. 기존 waiter는 resolver를 갖고 있어도 global pump trigger가 없고 orphan filter가 새 generation candidate를 계속 제외한다. SDD S15대로 accepted current owner만 resource generation을 활성화하고 관련 pending item을 즉시 live-resolve/pump한다.

## 사용자 리뷰 요청 흐름

Milestone 잠금 결정만 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 active review에 기록한다. 구현 중 직접 user prompt는 금지되며 code-review가 요청 검증과 실제 `USER_REVIEW.md` 작성을 소유한다.

## 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:
  - `reconnect-candidate-recovery`: accepted reconnect가 기존 waiter candidate를 복구하고 즉시 pump
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/phase/operational-observability-provider-management/PHASE.md`
- `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-resource-admission-ownership-alignment.md`
- `agent-roadmap/sdd/operational-observability-provider-management/provider-resource-admission-ownership-alignment/SDD.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-test/local/testing-smoke.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-spec/runtime/edge-node-execution.md`, `agent-spec/runtime/provider-pool-config-refresh.md`
- `apps/edge/internal/node/registry.go`, `store.go`
- `apps/edge/internal/transport/server.go`, `connection_handlers.go`
- `apps/edge/internal/transport/integration_test.go` — registration/reconnect/current-stale disconnect 범위
- `apps/edge/internal/bootstrap/runtime.go` — handler wiring 범위
- `apps/edge/internal/service/service.go`
- `apps/edge/internal/service/model_queue_types.go`, `model_queue_admission.go`, `model_queue_release.go`
- `apps/edge/internal/service/provider_resolution.go`
- `apps/edge/internal/service/service_internal_test.go` — refresh/re-enable live resolver pump 범위
- `apps/edge/internal/service/queue_dispatch_test.go` — disconnect fallback/cross-group 범위
- `apps/edge/internal/service/status_provider_test.go` — snapshot/reconnect candidate 범위

### SDD 기준

- SDD: `agent-roadmap/sdd/operational-observability-provider-management/provider-resource-admission-ownership-alignment/SDD.md`, 상태 `[승인됨]`, 잠금 `해제`.
- Acceptance Scenario S15 → `reconnect-candidate-recovery`.
- 대상 Node/provider offline, 다른 candidate full, waiter pending 상태에서 same identity accepted reconnect만으로 새 generation candidate를 재구성하고 dispatch해야 한다.
- stale/rejected connection은 activation/pump를 만들지 않는다. Evidence Map은 deterministic connectivity/queue test, race와 `Roadmap Completion`의 reconnect-only evidence를 요구한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/testing-smoke.md`를 읽고 Edge/testing profile의 fresh `-count=1` package/race/전체 회귀를 사용한다.
- mock e2e는 보조 entrypoint이고 reconnect-only correctness는 local transport+service integration test가 결정 evidence다.
- 외부 provider/Docker/secret은 필요 없다.

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

- config refresh/enable/capacity 증가가 live resolver를 pump하는 tests는 있으나 accepted transport registration 자체가 pump trigger인 test는 없다.
- disconnect에서 candidate slice를 제거한 waiter가 reconnect로 candidate universe를 재구성하는 test가 없다.
- rejected duplicate/stale accepted callback이 activation을 일으키지 않는 통합 회귀가 없다.

### 심볼 참조

- 변경 후보: `Server` handler fields/setters, `handleRegisterRequest`/`emitNodeRegistered`, `Service.HandleNodeConnect`, queue resource activation/pump helper.
- wiring callsite는 `apps/edge/internal/bootstrap/runtime.go`; server test helpers와 integration tests도 갱신 대상이다.

### 분할 판단

- accepted lifecycle signal과 resource activation/global pump는 서로 없으면 기능이 성립하지 않아 단일 task로 유지한다.
- sibling set: 08,09,10,11,12,13,14,15. Directory가 지정한 direct predecessors는 08/13/15다. 08은 archived `complete.log`로 완료됐고, 13과 15는 각각 09와 14를 거치는 active child다.
- write set은 transport registration, bootstrap wiring, service queue activation과 tests다. 12의 Node/config write set은 독립이다.

### 범위 결정 근거

- generation 발급/stale disconnect fence는 08, disconnect settlement는 13, offline snapshot wire closure는 15의 완료 계약으로 소비한다. 09와 10/14는 이 direct predecessor chain에 포함된다.
- reconnect 시 config refresh, new request, lease release 같은 외부 trigger를 호출해 우회하지 않는다.
- model catalog/config/proto schema와 Node retry policy는 변경하지 않는다.

### 최종 라우팅

- `evaluation_mode=first-pass`.
- build closures: scope=true, context=false(transport acceptance, generation resource state, global queue resolver/pump, 세 predecessor contract를 함께 유지), verification=true, evidence=true, ownership=true, decision=true. cloud가 context 폐쇄를 해소한다. scores=`2,2,1,1,2`; `cloud/G08` → `PLAN-cloud-G08.md`.
- review도 동일한 closure와 scores로 `cloud/G08` → `CODE_REVIEW-cloud-G08.md`; transport ordering과 queue wake-up race를 넓은 context에서 대조해야 한다.

## 구현 체크리스트

- [ ] accepted current-owner registration만 `(node_id,generation)` connected lifecycle callback을 event fanout 전에 service로 전달한다.
- [ ] queue resource가 configured provider identity를 새 generation available 상태로 복구하고 stale orphan marker를 안전하게 교체한다.
- [ ] activation이 모든 pending provider-pool item을 live resolver로 재구성해 새 요청/config refresh/lease release 없이 global enqueue 순서로 즉시 pump한다.
- [ ] full alternate candidate, reconnect candidate, stale/rejected connection, event fanout 포화와 race를 deterministic test로 검증한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우고 active pair를 유지한 채 구현 실행을 종료한다. 이 저장 상태가 runtime의 review-ready 신호이며 별도 handoff 파일·사용자 메시지를 만들지 않는다. 구현 에이전트는 code-review를 실행하거나 review 전용 작업을 수행하지 않는다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR-1] Accepted-connect authoritative lifecycle hook

문제: `apps/edge/internal/transport/connection_handlers.go:107-117`은 Registry 등록 후 관측 event만 발행하고 service correctness hook을 호출하지 않는다.

해결 방법:

```go
// Before: apps/edge/internal/transport/connection_handlers.go:112
if !s.registry.RegisterIfAbsent(entry) { ... }
s.emitNodeRegistered(rec)

// After
// accepted entry generation을 authoritative connected handler에 전달한 뒤
// 관측용 node.connected event를 발행한다.
```

Rejected duplicate는 handler를 호출하지 않는다. handler는 current Registry generation을 다시 확인해 stale callback을 no-op한다.

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/transport/server.go`: connect handler setter/guard.
- [ ] `apps/edge/internal/transport/connection_handlers.go`: accepted-only callback ordering.
- [ ] `apps/edge/internal/bootstrap/runtime.go`: `Service.HandleNodeConnect` wiring.
- [ ] `apps/edge/internal/transport/integration_test.go`: accepted/rejected/stale callback count와 event ordering.

테스트 작성: first accept와 reconnect는 각 generation당 한 번, duplicate reject는 0회를 검증한다.

중간 검증: `go test -count=1 ./apps/edge/internal/transport ./apps/edge/internal/bootstrap`가 통과한다.

### [REFACTOR-2] Generation activation and immediate global pump

문제: `apps/edge/internal/service/model_queue_release.go:87-137`은 disconnect에서 resource를 orphan하고 pump하지만 역방향 activation method가 없다. `model_queue_admission.go:313-335`는 orphan candidate를 걸러 기존 waiter를 고립시킨다.

해결 방법: `HandleNodeConnect(nodeID,generation)`이 queue lock 아래 configured resources를 generation-aware active 상태로 전환하고 모든 pending item을 `resolveCandidates`로 다시 구성한 뒤 `pumpAllLocked`한다. old generation counter/lease는 되살리지 않고 09의 terminal item은 이미 queue에서 제거됐으므로 복구하지 않는다.

```go
// Before: apps/edge/internal/service/model_queue_release.go:99-104,144-147
res.inFlight = 0
res.longInFlight = 0
res.orphan = true
m.pumpAllLocked() // 역방향 activation entrypoint 없음

// After: accepted current generation만 resource를 activate하고 live-resolve한다.
m.activateNodeGenerationLocked(nodeID, generation)
m.resolveAndPumpAllLocked()
```

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/service/service.go`: generation-aware connect entrypoint.
- [ ] `apps/edge/internal/service/model_queue_types.go`: resource activation state/helper.
- [ ] `apps/edge/internal/service/model_queue_release.go` 또는 connectivity 전용 기존 파일: activate+pump.
- [ ] `apps/edge/internal/service/service_internal_test.go`: alternate full + reconnect waiter immediate dispatch.
- [ ] `apps/edge/internal/service/queue_dispatch_test.go`: disconnect candidate 제거 뒤 reconnect live rebuild.
- [ ] `apps/edge/internal/service/status_provider_test.go`: activation generation과 counters/snapshot 수렴 보조 assertion.

테스트 작성: `apps/edge/internal/service/service_internal_test.go`에 `TestAcceptedReconnectActivatesCandidateAndPumpsWaiter`, `apps/edge/internal/service/queue_dispatch_test.go`에 `TestReconnectRebuildsDisconnectedCandidateUniverse`를 추가한다. full alternate+pending waiter fixture로 외부 trigger 없음, stale generation no-op, rejected duplicate no pump, event bus saturation을 검증한다.

중간 검증: `go test -race -count=1 ./apps/edge/internal/service -run 'Reconnect|CandidateRecovery|NodeConnect'`가 통과한다.

## 의존 관계 및 구현 순서

- predecessor 08은 `agent-task/archive/2026/07/m-provider-resource-admission-ownership-alignment/08_disconnect_exclusion/complete.log`로 충족됐다.
- predecessor 13과 15는 각각 matching active/archive `complete.log`가 필요하다.
- REFACTOR-1로 accepted-only signal을 고정한 뒤 REFACTOR-2로 generation activation/pump를 연결한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/internal/transport/server.go`, `connection_handlers.go`, `integration_test.go` | REFACTOR-1 |
| `apps/edge/internal/bootstrap/runtime.go` | REFACTOR-1 |
| `apps/edge/internal/service/service.go`, `model_queue_types.go`, `model_queue_release.go` | REFACTOR-2 |
| `apps/edge/internal/service/service_internal_test.go`, `queue_dispatch_test.go`, `status_provider_test.go` | REFACTOR-2 |

## 최종 검증

- `gofmt -d apps/edge/internal/transport/server.go apps/edge/internal/transport/connection_handlers.go apps/edge/internal/transport/integration_test.go apps/edge/internal/bootstrap/runtime.go apps/edge/internal/service/service.go apps/edge/internal/service/model_queue_types.go apps/edge/internal/service/model_queue_release.go apps/edge/internal/service/service_internal_test.go apps/edge/internal/service/queue_dispatch_test.go apps/edge/internal/service/status_provider_test.go` → 출력 없음.
- `go test -count=1 ./apps/edge/internal/transport ./apps/edge/internal/bootstrap` → PASS.
- `go test -count=1 ./apps/edge/internal/service -run 'Reconnect|CandidateRecovery|NodeConnect'` → PASS.
- `go test -race -count=1 ./apps/edge/internal/transport ./apps/edge/internal/service` → PASS.
- `go test -count=1 ./apps/edge/...` → PASS.
- `go test -count=1 ./...` → PASS.
- `IOP_E2E_PROFILE=mock ./scripts/e2e-smoke.sh` → 보조 entrypoint PASS 또는 명시 blocker; reconnect-only full-cycle evidence를 별도 기록한다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우고 active pair를 유지한 채 구현 실행을 종료한다. 이 저장 상태가 runtime의 `review-ready` 신호이며 별도 handoff 파일·사용자 메시지를 만들지 않는다. 구현 에이전트는 code-review를 실행하거나 review 전용 작업을 수행하지 않고, runtime은 별도 review 에이전트 instance에 즉시 인계한다.
