# 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:** `\t` (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.-` (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.