# Data Model: Safety, Reliability & Idiomatic Refactor

**Feature Branch**: `001-safety-reliability-refactor`  
**Date**: 2026-03-03  

## Entities

### HostsFile

Represents the system hosts file as parsed content sections.

| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| Path | `string` | Absolute path to the hosts file | OS-specific; determined by platform package |
| OriginalContent | `[]string` | Raw lines from the file as read | Immutable after read; used for change detection |
| PrefixContent | `[]string` | Lines before the managed block start marker | Preserved verbatim on write |
| ManagedContent | `[]string` | Lines between start and end markers (exclusive) | Modified by add/delete operations |
| PostfixContent | `[]string` | Lines after the managed block end marker | Preserved verbatim on write |
| HasManagedBlock | `bool` | Whether a valid managed block was found | `true` if both start and end markers present in order |

**Validation Rules:**
- If start marker is present, end marker must also be present (and vice versa).
- End marker must appear after start marker.
- Duplicate start or end markers are not permitted.
- Start marker at line index 0 is a valid position (must use sentinel, not zero-check).

**State Transitions:**
```
[File Read] → Parse → [No Managed Block] → Add entries → [Has Managed Block] → Write
                    → [Has Managed Block] → Add/Delete entries → [Updated Managed Block] → Write
                    → [Corrupt Block] → ERROR (refuse to modify)
```

### DNSEntry

A single IP-to-hostname mapping in the hosts file.

| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| IP | `string` | IPv4 address | Must be a valid IPv4 address; result of DNS resolution |
| Hostname | `string` | Fully qualified domain name | As provided by user (without trailing dot) |

**String Representation:** `<IP>\t<Hostname>` (tab-separated, one per line in hosts file)

**Validation Rules:**
- IP must be non-empty and a valid IPv4 address.
- Hostname must be non-empty and not contain whitespace.
- Entries are deduplicated by their full string representation (IP + hostname pair).

### ManagedBlock

The delimited section of the hosts file owned by this tool.

| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| StartMarker | `string` | Comment line marking block start | `# DNSHelper <<-> START CONFIG` (constant) |
| EndMarker | `string` | Comment line marking block end | `# DNSHelper <->> END CONFIG` (constant) |
| Entries | `[]DNSEntry` | DNS entries within the block | Zero or more; deduplicated |

**Rules:**
- The managed block is only written if there are entries to include.
- When all entries are removed (delete all), the entire block (markers included) is removed.
- The block is always written as: start marker, entries (one per line), end marker.

### ResolveResult

The outcome of resolving a single hostname against a DNS server.

| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| Hostname | `string` | The hostname that was queried | As provided by user |
| IPs | `[]string` | Resolved IPv4 addresses | Deduplicated; empty if resolution failed |
| Error | `error` | Resolution error, if any | Non-nil on failure (timeout, NXDOMAIN, etc.) |

**State Transitions:**
```
[Query Sent] → A records returned → [Success: IPs populated]
             → CNAME returned → Follow chain (max 10 depth) → [Success or Error]
             → NXDOMAIN / timeout / error → [Failure: Error populated]
```

### BackupFile

A temporary safety copy of the hosts file created before modification.

| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| Path | `string` | Absolute path to backup file | In executable's directory |
| SourcePath | `string` | Path of the file that was backed up | The hosts file path |
| Timestamp | `string` | Date stamp portion of filename | Format: `YYYYMMDD` |
| RandomSuffix | `string` | Short random string for uniqueness | 4 hex characters (e.g., `a7f3`) |

**Naming Convention:** `hosts.bak.<Timestamp>-<RandomSuffix>` (e.g., `hosts.bak.20260303-a7f3`)

**Lifecycle:**
```
[Pre-Write] → Create backup copy → [Write Success] → Delete backup
                                  → [Write Failure] → Original intact, clean up backup
```

### LockFile

A file-based mutex for serializing concurrent access.

| Field | Type | Description | Constraints |
|-------|------|-------------|-------------|
| Path | `string` | Absolute path to lock file | In executable's directory; `.dns-helper.lock` |
| PID | `int` | Process ID of the lock holder | Written for debugging; not used for staleness |
| CreatedAt | `time.Time` | When the lock was acquired | Approximated by file ModTime |

**Parameters:**
- Stale timeout: 2 minutes
- Retry interval: 200ms
- Max wait: 2 seconds (10 retries)

**Lifecycle:**
```
[Pre-Lock] → Attempt create (O_EXCL) → [Acquired] → Hold during R/M/W → [Release: delete]
                                      → [Exists, not stale] → Retry (up to 2s) → [Acquired or Error]
                                      → [Exists, stale] → Break stale → Retry → [Acquired or Error]
```

## Relationships

```
CLI (main.go)
 ├── validates input (host, server, subcommand)
 ├── calls Resolver to get ResolveResult[] (before lock)
 ├── acquires LockFile
 ├── reads HostsFile → parses ManagedBlock
 ├── modifies ManagedBlock (add DNSEntry[] / delete by hostname)
 ├── creates BackupFile
 ├── writes HostsFile atomically (temp + rename)
 ├── deletes BackupFile on success
 └── releases LockFile
```

## Operation Flows

### Add Flow
1. Validate: `-host` and `-server` required and non-empty.
2. Resolve: For each hostname, query DNS server → collect `ResolveResult[]`.
3. Lock: Acquire lock file.
4. Read: Read hosts file → parse into sections.
5. Modify: Remove existing entries for the specified hostnames from managed content. Add new `DNSEntry[]` from resolve results.
6. Deduplicate: Remove duplicate entries by full line content.
7. Backup: Copy current hosts file to backup location.
8. Write: Assemble full content (prefix + managed block + postfix) → write to temp file → rename over hosts file.
9. Cleanup: Delete backup on success. Release lock.
10. Report: Print summary (entries added/updated). Exit non-zero if any hostname failed to resolve.

### Delete Flow
1. Validate: `-host` required and non-empty, OR `all` keyword.
2. Lock: Acquire lock file.
3. Read: Read hosts file → parse into sections.
4. Modify: If specific hosts — remove matching lines from managed content. If `all` — clear managed content entirely (block markers will not be written).
5. Backup: Copy current hosts file to backup location.
6. Write: Assemble full content → write to temp file → rename.
7. Cleanup: Delete backup on success. Release lock.
8. Report: Print summary (entries removed). Report if no entries were found.
