# Implementation Plan: Smart DNS Server Resolution Modes

**Branch**: `002-server-resolution-modes` | **Date**: 2026-03-04 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/002-server-resolution-modes/spec.md`

## Summary

Extend the dns-helper CLI's `-server` flag to support multiple resolution modes: smart default resolution (parallel NS fan-out across local + public resolvers for authoritative answers), `-server local` (local resolvers only), `-server gateway` (default gateway as DNS), and the existing explicit IP mode. Key technical work: implement parallel NS fan-out across all label levels and all resolvers, authoritative NS query with recursion disabled, CNAME-aware re-resolution, parallel A-query fallback for internal hosts without zone delegation, OS-level local resolver and gateway discovery (cross-platform), configurable per-query timeout, verbose diagnostic output to stderr, context-aware error messages for private TLDs, and split-horizon conflict detection (Stage 2.5: cross-check authoritative answer against local resolvers to catch enterprise DNS overrides for public TLD hostnames).

## Technical Context

**Language/Version**: Go 1.20 (ceiling per constitution; `go.mod` specifies `go 1.20`)
**Primary Dependencies**: Go standard library, `golang.org/x/net/dns/dnsmessage` (approved; already in go.mod v0.35.0)
**Storage**: Local filesystem (system hosts file, backup files, lock file) — unchanged from 001
**Testing**: `go test ./...` with TDD (Red-Green-Refactor); fake DNS servers via UDP listeners; interfaces for OS-level network config discovery
**Target Platform**: Windows, Linux, macOS (cross-compiled single binary)
**Project Type**: CLI tool (single binary, no config files)
**Performance Goals**: Parallel NS fan-out must complete within the latency of the slowest single resolver (SC-002); error reporting within 10 seconds (SC-005)
**Constraints**: Go 1.20 ceiling; zero unapproved external dependencies; hosts file safety (unchanged); UDP-only DNS (no TCP fallback)
**Scale/Scope**: ~800 LOC current across 4 packages; this feature adds ~800-1200 LOC of new resolver logic, platform discovery, and tests

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

### Principle I: System File Safety
- **Status**: PASS — No changes to the hosts file write path. The atomic write (temp+rename), backup, and lock mechanisms from 001 remain intact and unmodified. This feature only changes what IP address is written, not how it is written.

### Principle II: Standard Library First
- **Status**: PASS — All new functionality uses Go standard library and `golang.org/x/net/dns/dnsmessage` (approved source, already in go.mod). Parallel operations use goroutines and channels (stdlib). OS-level network config discovery uses `os/exec` to call platform utilities (`ipconfig`, `ip route`, `networksetup`, `scutil`), which is standard library.
- **Risk**: Local resolver/gateway discovery requires parsing OS command output. This is fragile but the standard library approach — no approved Go library exists for cross-platform network adapter enumeration under Go 1.20.

### Principle III: Go 1.20 Compatibility
- **Status**: PASS — No new language features or stdlib APIs beyond Go 1.20 are needed. `sync.WaitGroup`, channels, goroutines, `context` (available since Go 1.7), `os/exec` — all available in Go 1.20. `golang.org/x/net` v0.35.0 is already pinned and Go 1.20-compatible.

### Principle IV: Idiomatic Error Handling
- **Status**: PASS — All new functions (resolver pool construction, NS fan-out, local resolver discovery, gateway discovery) will return errors to callers. No `os.Exit` outside `main()`. Error messages will include context (hostname, resolver IP, stage information). The parallel fan-out collects individual query errors without treating them as fatal — only aggregate failure (no results from any resolver) produces a user-facing error.

### Principle V: Simplicity and Single Purpose
- **Status**: PASS — The tool's purpose is unchanged: update the local hosts file with DNS entries. The new resolution modes make the tool smarter about *how* it resolves, but add no new subcommands, no configuration files, no daemons. The `-server` flag gains new keyword values (`local`, `gateway`) and the default behavior becomes smart resolution when omitted. No speculative features added.
- **Note**: The parallel NS fan-out adds inherent complexity. This is justified by the core user scenario (US-1) which requires authoritative resolution for both public and internal hostnames without user configuration.

### Principle VI: Test-Driven Development
- **Status**: PASS — All new code will follow TDD (Red-Green-Refactor). Platform-specific discovery functions will accept interfaces for testability. The resolver package already has fake DNS server test helpers that will be extended for NS queries. Parallel fan-out logic will be tested with deterministic fake resolver pools.

### Gate Evaluation
All six constitution principles are satisfied. No violations exist in the current codebase that this feature introduces or worsens. **GATE: PASS** — proceed to Phase 0.

## Project Structure

### Documentation (this feature)

```text
specs/002-server-resolution-modes/
├── plan.md              # This file
├── research.md          # Phase 0 output
├── data-model.md        # Phase 1 output
├── quickstart.md        # Phase 1 output
├── contracts/           # Phase 1 output
└── tasks.md             # Phase 2 output (created by /speckit.tasks)
```

### Source Code (repository root)

```text
dns-helper/
├── main.go                    # Entry point, CLI parsing, os.Exit — updated for new -server modes, -timeout, -verbose
├── resolver/
│   ├── resolver.go            # Existing: single-server A-record lookup (kept for -server <ip> mode)
│   ├── resolver_test.go       # Existing tests
│   ├── pool.go                # NEW: ResolverPool (bootstrap set + local resolvers), parallel fan-out
│   ├── pool_test.go           # NEW: Tests for pool construction and parallel fan-out
│   ├── authority.go           # NEW: Authoritative NS discovery (Stage 1), authoritative query (Stage 2), fallback (Stage 3)
│   ├── authority_test.go      # NEW: Tests for NS fan-out, authoritative query, CNAME re-resolution
│   ├── modes.go               # NEW: Mode dispatcher (smart default, local, gateway, explicit IP)
│   └── modes_test.go          # NEW: Tests for mode selection and dispatch
├── platform/
│   ├── platform.go            # Package doc — extended with network discovery interface
│   ├── platform_windows.go    # Existing: GetHostsFilePath + NEW: GetLocalResolvers, GetDefaultGateway
│   ├── platform_linux.go      # Existing: GetHostsFilePath + NEW: GetLocalResolvers, GetDefaultGateway
│   ├── platform_darwin.go     # Existing: GetHostsFilePath + NEW: GetLocalResolvers, GetDefaultGateway
│   └── platform_test.go       # Extended with discovery tests
├── hostfile/
│   ├── hostfile.go            # Unchanged
│   ├── hostfile_test.go       # Unchanged
│   ├── managed.go             # Unchanged
│   ├── managed_test.go        # Unchanged
│   └── filesystem.go          # Unchanged
├── lockfile/
│   ├── lockfile.go            # Unchanged
│   └── lockfile_test.go       # Unchanged
├── go.mod
└── go.sum
```

**Structure Decision**: Extend existing package structure. New resolver logic goes into the `resolver/` package in separate files (`pool.go`, `authority.go`, `modes.go`) to maintain single-responsibility per file without introducing new top-level packages. Platform-specific network discovery extends the existing `platform/` package files. The `hostfile/` and `lockfile/` packages are untouched. This follows the established patterns from 001.

## Complexity Tracking

> No constitution violations require justification. The parallel NS fan-out is the most complex new component but is justified by the core user scenario (US-1) and explicitly required by FR-013/FR-014.

## Constitution Re-Check (Post Phase 1 Design)

*All six principles re-evaluated after design phase. No new violations introduced.*

| Principle | Status | Evidence |
|-----------|--------|----------|
| I. System File Safety | PASS | No changes to the hosts file write path. Atomic write, backup, and lock mechanisms from 001 untouched. Only the DNS resolution layer changes — different IPs may be written, but the write mechanism is identical. |
| II. Standard Library First | PASS | All new code uses Go stdlib + `golang.org/x/net/dns/dnsmessage` (approved, already in go.mod). Platform discovery uses `os/exec` to call OS utilities — no new dependencies. Parallel fan-out uses goroutines + channels (stdlib). |
| III. Go 1.20 Compatibility | PASS | All APIs verified: `context.WithTimeout` (Go 1.7), `net.Dialer.DialContext` (Go 1.7), `errors.Join` (Go 1.20), `os/exec` (Go 1.0), `dnsmessage.TypeNS`/`parser.NSResource()` (present in x/net v0.35.0). No Go 1.21+ features used. `slices`, `maps`, `slog`, `context.WithTimeoutCause` explicitly avoided. |
| IV. Idiomatic Error Handling | PASS | All new functions return errors. `Resolve()` returns errors to `main.go`. `Discover()` returns errors to caller. Parallel fan-out collects per-query errors in `NSResult.Err` without fatal exits. Error messages include hostname, resolver IP, stage context, and private TLD hints. Only `main()` calls `os.Exit()`. |
| V. Simplicity and Single Purpose | PASS | Tool purpose unchanged: update hosts file with DNS entries. No new subcommands, config files, daemons, or TUI. The `-server` flag gains keyword values; `-timeout` and `-verbose` are standard CLI patterns. New files follow single-responsibility: `parse.go` (parsing), `pool.go` (pool construction), `authority.go` (NS fan-out), `fallback.go` (A fallback), `modes.go` (dispatch). |
| VI. Test-Driven Development | PASS | All new code designed for TDD. `FakeNetworkDiscoverer` enables deterministic pool/mode testing. Existing `startFakeDNS`/`startFakeDNSMulti` test helpers extend to NS queries. `ParseServerFlag` and `ExtractLabelLevels` are pure functions with straightforward table-driven tests. Platform integration tests are build-tagged. |
