<!-- task=m-node-provider-execution-liveness-recovery/07+06_reception_fence plan=5 tag=REVIEW_REFACTOR milestone-task=failure-handoff -->

# Reject Ambiguous Reception Owners

## For the Implementing Agent

Implement only the Required R1 direct fix, run every verification command, and fill all implementation-owned sections of `CODE_REVIEW-cloud-G07.md` with actual notes and raw output. Keep active files in place and report ready for review; finalization belongs to the code-review skill. If blocked, record exact blocker evidence, attempted commands/output, and resume conditions only. Do not ask the user, call user-input tools, create control-plane stop files, classify the next state, archive logs, or write `complete.log`.

## Background

Plan 4 added reception identity derived from the receiving TCP client, but the registry still permits one client to own multiple node ids. The reverse lookup then selects an arbitrary map entry, so its node id and generation are not authoritative. Registration and lookup must enforce one singular client owner and fail closed if an invalid ambiguous state is encountered.

## Archive Evidence Snapshot

- `plan_local_G08_4.log` and `code_review_cloud_G08_4.log` in this directory contain plan 4 and its `FAIL` verdict: one Required R1, zero Suggested findings.
- Required R1 reproducer: registering `node-a` and `node-b` with the same `TcpClient` succeeds, then `CurrentOwnerForClient` returns an arbitrary `node-a` generation instead of failing closed.
- Fresh focused/package/race/vet checks and the actual Edge/Node reconnect diagnostic passed for the reception-fence paths. A fresh package smoke rerun was temporarily blocked by unrelated concurrently written liveness-observability tests; this follow-up must rerun it from the resulting checkout.
- Roadmap carryover remains `milestone-task=failure-handoff`, SDD S04. This packet closes only the reception-owner producer invariant; runtime health overlay and recovery remain in dependent sibling tasks.

## Finding Resolution Map

| Finding | Mode | Exact fix / dependency evidence | Changed precondition |
|---------|------|---------------------------------|----------------------|
| Required R1 | direct-fix | Update `apps/edge/internal/node/registry.go`, its regression tests, and the Edge-Node wire registration text so one non-nil client cannot own multiple node ids and ambiguous lookup fails closed. | The failing same-client/two-node state becomes rejected at registration, and defensive lookup returns no authority if such a state is constructed. |

## Analysis

### Files Read

- `apps/edge/internal/node/registry.go`
- `apps/edge/internal/node/registry_test.go`
- `apps/edge/internal/transport/connection_handlers.go`
- `apps/edge/internal/transport/server.go`
- `apps/edge/internal/transport/server_test.go`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-roadmap/phase/operational-observability-provider-management/milestones/node-provider-execution-liveness-recovery.md`
- `agent-roadmap/sdd/operational-observability-provider-management/node-provider-execution-liveness-recovery/SDD.md`
- `agent-task/m-node-provider-execution-liveness-recovery/07+06_reception_fence/plan_local_G08_4.log`
- `agent-task/m-node-provider-execution-liveness-recovery/07+06_reception_fence/code_review_cloud_G08_4.log`

### SDD Criteria

- SDD: `agent-roadmap/sdd/operational-observability-provider-management/node-provider-execution-liveness-recovery/SDD.md`; status `[승인됨]`; `milestone-task=failure-handoff`.
- Target: S04 and Evidence Map S04 require fail-closed connection-generation binding for normalized and tunnel terminal reception.
- The implementation checklist therefore requires singular client ownership, ambiguous-state rejection, unchanged current/stale/unregistered behavior, and fresh two-path transport verification.

### Verification Context

- Handoff source: plan 4 review evidence plus fresh repository-native reviewer runs; no separate `verification_context` document was supplied.
- Precondition: `agent-task/archive/2026/08/m-node-provider-execution-liveness-recovery/06+05_failure_wire_mapping/complete.log` satisfies predecessor `06+05_failure_wire_mapping`.
- Confirmed evidence: focused Edge node/transport/bootstrap tests, three-count race tests, vet, `git diff --check`, and `IOP_DEV_RECONNECT_BIND_TIMEOUT=45 ./scripts/dev/edge-node-reconnect-diagnostic.sh` passed. The R1 reproducer failed deterministically before being removed.
- Constraint: other active sibling work added transient liveness-observability test failures during review. Those files are outside this packet, but the implementing agent must record any remaining shared-worktree blocker and rerun the repository smoke once the checkout compiles.
- Confidence: high; the root cause and expected fail-closed behavior are isolated under one registry lock and exercised without external services or credentials.

### Test Coverage Gaps

- Existing tests cover nil, unregistered, current, stale, and reconnected clients.
- Missing coverage: one client claiming two distinct node ids, preservation of the first owner/generation after rejection, and fail-closed lookup when an ambiguous state is constructed through the unconditional test helper.

### Symbol References

- No symbol is renamed or removed.
- `RegisterIfAbsent` is consumed by `apps/edge/internal/transport/connection_handlers.go` registration handling; `false` already maps to a rejected registration.
- `CurrentOwnerForClient` is consumed by the RunEvent and ProviderTunnelFrame listener closures in `apps/edge/internal/transport/connection_handlers.go`; `false` already drops correctness processing.

### Split Judgment

- Keep one compact packet. Registration uniqueness and reverse lookup fail-closed behavior are the two halves of one authoritative client-owner invariant and share the same registry lock and tests.
- This dependent subtask remains `07+06_reception_fence`; predecessor index `06` is satisfied by `agent-task/archive/2026/08/m-node-provider-execution-liveness-recovery/06+05_failure_wire_mapping/complete.log`.

### Scope Rationale

- Do not consume node/generation in queue, overlay, or retry logic; dependent siblings own those consumers.
- Do not change callback signatures, protobuf schema, provider identity, liveness metadata, or observability fanout.
- Update only the wire registration wording needed to make the singular connection ownership rule explicit; the living spec remains accurate at its current feature-level detail.

### Final Routing

- `evaluation_mode=isolated-reassessment`; finalizer=`finalize-task-policy.sh pair`.
- Build closures: scope/context/verification/evidence/ownership/decision are all true. Scores `(1,2,1,1,2)`, grade G07, base/final route `local-fit`, canonical file `PLAN-local-G07.md`.
- Review closures: scope/context/verification/evidence/ownership/decision are all true. Scores `(1,2,1,1,2)`, grade G07, route `official-review`, canonical file `CODE_REVIEW-cloud-G07.md` (`codex`, `gpt-5.6-sol`, `xhigh`).
- `large_indivisible_context=false`; positive loop risks: `temporal_state`, `concurrent_consistency`, `boundary_contract` (3). `review_rework_count=1`, `evidence_integrity_failure=false`; neither risk nor recovery boundary matched. Capability gap: none.

## Implementation Checklist

- [x] REVIEW_REFACTOR-1 rejects same-client ownership of multiple node ids atomically, makes ambiguous reverse lookup fail closed, preserves the original owner/generation on rejection, documents the registration invariant, and adds deterministic regressions.
- [x] Run focused, package, race, vet, provider-only smoke, actual Edge/Node reconnect diagnostic, and diff verification with fresh output.
- [x] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_REFACTOR-1] Enforce singular client ownership

**Problem:** `apps/edge/internal/node/registry.go:91` rejects only an occupied node id. A second distinct node id can therefore register the same non-nil client. `CurrentOwnerForClient` at line 200 returns the first matching map entry, so reception identity becomes nondeterministic instead of authoritative.

**Solution:** Under the existing registry lock, reject `RegisterIfAbsent` when a non-nil client is already present on any current entry. Make `CurrentOwnerForClient` collect at most one match and return `nil, false` for zero or multiple matches, cloning only an exactly-one owner. Preserve the original entry and its generation when a second registration is rejected, and state the one-connection/one-node invariant in the wire contract.

Before:

```go
// apps/edge/internal/node/registry.go:91
if _, exists := r.byID[entry.NodeID]; exists {
	return false
}

// apps/edge/internal/node/registry.go:200
for _, entry := range r.byID {
	if entry.Client == client {
		return entry.Clone(), true
	}
}
```

After:

```go
if _, exists := r.byID[entry.NodeID]; exists {
	return false
}
if entry.Client != nil {
	for _, current := range r.byID {
		if current.Client == entry.Client {
			return false
		}
	}
}

var owner *NodeEntry
for _, entry := range r.byID {
	if entry.Client != client {
		continue
	}
	if owner != nil {
		return nil, false
	}
	owner = entry
}
if owner == nil {
	return nil, false
}
return owner.Clone(), true
```

**Modified Files and Checklist:**

- [x] `apps/edge/internal/node/registry.go`: enforce non-nil client uniqueness in `RegisterIfAbsent` and make reverse lookup reject ambiguity under the registry lock.
- [x] `apps/edge/internal/node/registry_test.go`: add `TestRegistryRegisterIfAbsentRejectsClientRebinding` and `TestCurrentOwnerForClientFailsClosedForAmbiguousClient`; assert count, owner, and generation preservation.
- [x] `agent-contract/inner/edge-node-runtime-wire.md`: state that one accepted TCP connection owns exactly one node id and a second identity claim is rejected without changing the first binding.
- [x] `agent-task/m-node-provider-execution-liveness-recovery/07+06_reception_fence/CODE_REVIEW-cloud-G07.md`: fill implementation evidence and raw verification output.

**Test Strategy:** Add deterministic registry tests using one `TcpClient`. The production registration test must reject `node-b` after `node-a` without advancing or replacing the first generation. A defensive test may use unconditional `Register` to construct an invalid two-entry state and must prove `CurrentOwnerForClient` returns `nil, false`. Existing transport reception tests prove a false lookup cannot reach RunEvent/tunnel correctness callbacks.

**Verification:** The focused named tests must execute, and package/race coverage must retain current/stale/unregistered reception behavior.

## Dependencies and Execution Order

1. `06+05_failure_wire_mapping` remains satisfied by `agent-task/archive/2026/08/m-node-provider-execution-liveness-recovery/06+05_failure_wire_mapping/complete.log`.
2. Complete Required R1 in this packet before dependent `08+07_health_overlay` consumes the authority values.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/edge/internal/node/registry.go` | REVIEW_REFACTOR-1 |
| `apps/edge/internal/node/registry_test.go` | REVIEW_REFACTOR-1 |
| `agent-contract/inner/edge-node-runtime-wire.md` | REVIEW_REFACTOR-1 |
| `agent-task/m-node-provider-execution-liveness-recovery/07+06_reception_fence/CODE_REVIEW-cloud-G07.md` | REVIEW_REFACTOR-1 |

## Final Verification

Fresh Go output is required; cached-only evidence is not acceptable.

1. `go test -count=1 -v ./apps/edge/internal/node -run '^(TestRegistryRegisterIfAbsentRejectsClientRebinding|TestCurrentOwnerForClientFailsClosedForAmbiguousClient|TestCurrentOwnerForClient)$'` — PASS and every named owner fixture executes.
2. `go test -count=1 ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/bootstrap` — PASS.
3. `go test -race -count=3 ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/bootstrap` — PASS with no race report.
4. `go vet ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/bootstrap` — no diagnostics.
5. `./scripts/e2e-smoke.sh` — PASS for the repository provider-only package smoke.
6. `IOP_DEV_RECONNECT_BIND_TIMEOUT=45 ./scripts/dev/edge-node-reconnect-diagnostic.sh` — PASS with dispatch before and after Node re-registration.
7. `git diff --check` — no whitespace errors.

After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
