19 KiB
Feature Specification: Safety, Reliability & Idiomatic Refactor
Feature Branch: 001-safety-reliability-refactor
Created: 2026-03-03
Status: Draft
Input: User description: "Address safety and reliability bugs, refactor for idiomatic Go 1.20 patterns, improve project structure and developer maintainability"
User Scenarios & Testing (mandatory)
User Story 1 - Hosts File Is Never Corrupted or Lost (Priority: P1)
As a system administrator, I need the hosts file to remain intact even if the tool crashes, loses power, or encounters a disk error mid-write, because a corrupted hosts file can break all name resolution on the machine and require manual recovery.
Why this priority: The hosts file is a critical system resource. Losing it can render a machine unable to resolve any DNS names, disrupting all network-dependent software. Protecting it is the single most important improvement.
Independent Test: Run the tool to add a DNS entry, then simulate an interruption (e.g., kill the process during write). Verify the original hosts file is unchanged or correctly updated — never partially written or empty.
Acceptance Scenarios:
- Given an existing hosts file with valid content, When the tool writes updated entries successfully, Then the hosts file contains the correct updated content, no data from the original file is lost, and the hosts file was never truncated or partially written at any point.
- Given an existing hosts file with valid content, When the tool is interrupted (crash, signal, power loss) during the write operation, Then the original hosts file remains intact and unmodified because the write targeted a temporary file, not the hosts file directly.
- Given the disk is full or the write fails for any reason, When the tool attempts to save the hosts file, Then an error is reported to the user, the original hosts file is not altered, and any temporary files are cleaned up.
- Given an existing hosts file, When the tool prepares to write, Then a temporary backup copy of the current hosts file is created before any modification begins. On successful rename of the temp file over the hosts file, the backup is deleted. On failure, the backup is available and the original hosts file was never modified.
User Story 2 - Correct DNS Resolution Results (Priority: P1)
As a user adding DNS entries, I need the resolved IP addresses written to the hosts file to be accurate and free of duplicates, because incorrect entries will cause connectivity failures — the exact problem this tool is meant to solve.
Why this priority: The core value proposition of the tool is providing correct, current DNS mappings. Bugs that produce duplicate or incorrect IPs directly undermine the tool's purpose.
Independent Test: Add a hostname that resolves via CNAME chain to multiple IPs using a specific DNS server. Verify the hosts file contains exactly the correct set of unique IPs with no duplicates.
Acceptance Scenarios:
- Given a hostname that resolves directly to A records, When the user runs
add -host <name> -server <dns>, Then the hosts file contains one entry per unique IP address for that hostname. - Given a hostname that resolves via a CNAME chain to A records, When the user runs
add -host <name> -server <dns>, Then the CNAME chain is followed correctly and the final A record IPs are written without duplicates. - Given multiple hostnames specified via comma-separated values, When the user runs
add -host host1,host2 -server <dns>, Then all hostnames are resolved and each gets correct, deduplicated entries in the hosts file. - Given the DNS resolution layer has been reimplemented using only approved dependencies, When any hostname is resolved, Then the results are identical to the previous implementation (same IPs, same CNAME handling, same error reporting).
- Given multiple hostnames where some resolve successfully and others fail, When the user runs
add -host host1,host2 -server <dns>, Then entries are written for the successfully resolved hostnames, a warning identifying each failed hostname is printed to stdout, and the tool exits with a non-zero exit code.
User Story 3 - Reliable Deletion of Managed Entries (Priority: P2)
As a user, when I delete managed DNS entries (one hostname or all), I need the tool to correctly remove only the entries it previously added, leaving all other hosts file content untouched.
Why this priority: Incorrect deletion could remove entries the user or other tools added, or leave orphaned entries behind. This must work correctly to maintain trust in the tool.
Independent Test: Add entries for two hostnames, then delete one. Verify the deleted hostname's entries are gone and the other hostname's entries remain. Then test delete all and verify only the managed block is removed.
Acceptance Scenarios:
- Given the hosts file contains managed entries for hosts A and B, When the user deletes host A, Then only host A's entries are removed; host B's managed entries and all unmanaged content remain intact.
- Given the hosts file contains managed entries for multiple hosts, When the user runs
delete all, Then the entire managed block (start marker through end marker) is removed and all unmanaged content remains intact. - Given the hosts file contains no managed entries, When the user runs
delete -host <name>, Then the tool reports that no entries were found and the hosts file is not modified.
User Story 4 - Clear Error Messages and Input Validation (Priority: P2)
As a user, when I provide invalid or incomplete input (missing flags, empty values, unresolvable hostnames), I need a clear error message explaining what went wrong and how to fix it, and the tool must not proceed with partial or incorrect data.
Why this priority: Without proper validation, the tool can silently produce incorrect results or corrupt the managed block. Clear errors prevent misuse and reduce troubleshooting time.
Independent Test: Run the tool with missing -host, empty -server, an unresolvable hostname, and invalid subcommands. Verify each produces a descriptive error, returns a non-zero exit code, and does not modify the hosts file.
Acceptance Scenarios:
- Given the user runs
addwithout a-hostflag, When the command executes, Then an error is displayed explaining that-hostis required, and the hosts file is not modified. - Given the user runs
add -host example.comwithout a-serverflag, When the command executes, Then an error is displayed explaining that-serveris required for the add command, and the hosts file is not modified. - Given the user provides an unrecognized subcommand, When the command executes, Then a usage message is displayed and the hosts file is not modified.
- Given the hostname cannot be resolved by the specified DNS server, When the command executes, Then an error is displayed indicating the resolution failure, and no entry is written for that hostname.
User Story 5 - Consistent Behavior Across Operating Systems (Priority: P3)
As a user running the tool on Windows, Linux, or macOS, I need the tool to behave consistently — locating the hosts file, reporting errors, and handling edge cases the same way regardless of platform.
Why this priority: Inconsistent platform behavior makes the tool unreliable and harder to maintain. Standardizing the platform layer improves confidence and reduces platform-specific bugs.
Independent Test: Build and run the tool on each supported platform. Verify that error conditions (missing hosts file, permission denied) produce equivalent error messages and exit codes on all platforms.
Acceptance Scenarios:
- Given the tool is running on any supported platform, When the hosts file cannot be found at the expected location, Then an error is returned to the caller with a descriptive message (not a process exit from within the platform code).
- Given the tool is running on any supported platform, When a permission error prevents reading or writing the hosts file, Then a clear error message is displayed and the tool exits cleanly.
User Story 6 - Clean, Maintainable Project Structure (Priority: P3)
As a developer maintaining this tool, I need the codebase to follow idiomatic patterns and be organized into logical units so that future changes (new features, bug fixes) are straightforward and low-risk.
Why this priority: The current codebase has dead code, inconsistent patterns, and tightly coupled logic that makes changes risky. A clean structure reduces the cost and risk of future work.
Independent Test: Review the refactored codebase for: no dead code, consistent error handling patterns, clear separation of concerns, and all tests passing after reorganization.
Acceptance Scenarios:
- Given the refactored codebase, When a developer reads the project, Then each file/package has a clear, single responsibility and no unused exports or dead code exist.
- Given the refactored codebase, When errors occur in any function, Then errors are returned to the caller (not handled by terminating the process internally) and the caller decides how to handle them.
- Given the project targets Go 1.20, When the project is built, Then it compiles cleanly with no deprecation warnings and uses language features available up to Go 1.20.
Edge Cases
- What happens when the hosts file is read-only or locked by another process?
- What happens when the DNS server is unreachable or times out?
- What happens when the managed block markers are present but malformed (e.g., start without end, end without start, duplicates)?
- What happens when the hosts file contains the managed block markers inside a comment added by someone else?
- What happens when the tool is run concurrently (two instances at the same time)? → Serialized via lock file; second instance waits briefly then fails if lock unavailable.
- What happens when the hostname resolves to zero IP addresses (NXDOMAIN)?
- What happens when the
-hostvalue contains whitespace or special characters?
Requirements (mandatory)
Functional Requirements
- FR-001: The tool MUST write new hosts file content to a temporary file in the same directory as the hosts file, then rename the temporary file over the original. The hosts file MUST never be truncated or written to directly. If the rename fails, the temporary file MUST be cleaned up and an error reported.
- FR-002: The tool MUST create a temporary backup of the current hosts file before performing any modification. The backup MUST be stored in the same directory as the tool's binary executable, named with a date stamp and short random string (e.g.,
hosts.bak.20260303-a7f3) to avoid collisions from concurrent runs. On successful rename of the temp file over the hosts file, the backup MUST be deleted. If the rename fails, the original hosts file was never modified so the backup serves as a belt-and-suspenders safety net; it MUST be deleted after confirming the original is intact. - FR-003: The tool MUST correctly follow CNAME chains during DNS resolution without producing duplicate IP entries.
- FR-004: The tool MUST correctly remove entries for all specified hostnames when deleting from a multi-host managed block (not just the last hostname processed).
- FR-005: The tool MUST NOT execute the hosts file write operation when an unrecognized subcommand is provided.
- FR-006: The tool MUST validate that required flags (
-hostfor add/delete,-serverfor add) are present and non-empty before proceeding. - FR-007: The tool MUST terminate immediately upon encountering a fatal error, without continuing to execute subsequent operations.
- FR-008: All platform-specific code MUST return errors to the caller rather than terminating the process directly.
- FR-009: The tool MUST remove all dead/unused code (unused
pingsubcommand, unusedIsNewfield, unusedfileExistsin shared scope). - FR-010: The tool MUST target Go 1.20 as the minimum language version and update dependencies accordingly.
- FR-011: The tool MUST detect and report corrupt managed block markers (start without end, end without start) and refuse to modify the file.
- FR-012: The tool MUST deduplicate resolved IP entries before writing them to the hosts file.
- FR-013: The tool MUST provide an informational summary of changes made (entries added, entries removed) after a successful operation.
- FR-014: The tool MUST eliminate the
github.com/miekg/dnsdependency by reimplementing DNS query functionality using only the Go standard library or approved sources (golang.org/x/*). The replacement MUST support sending A and CNAME queries to a user-specified DNS server and parsing the responses. - FR-015: When resolving multiple hostnames in a single invocation, the tool MUST write entries for all hostnames that resolved successfully, print a warning to stdout identifying each hostname that failed to resolve, and exit with a non-zero exit code to indicate partial failure.
- FR-016: The tool MUST use a lock file in the executable's directory to serialize concurrent access to the hosts file. DNS resolution MUST be performed before acquiring the lock so that the lock is held only during the file read-modify-write cycle. If the lock cannot be acquired after a brief wait, the tool MUST fail with a descriptive error. The lock MUST be released (and the lock file deleted) on both success and failure paths.
Key Entities
- Hosts File: The system DNS resolution file (
/etc/hostsor%SystemRoot%\System32\drivers\etc\hosts). Contains unmanaged content (user/system entries) and an optional managed block owned by this tool. - Managed Block: A clearly delimited section within the hosts file, bounded by start and end marker comments, containing only entries written by this tool.
- DNS Entry: A mapping of an IP address to a hostname, represented as a single line in the hosts file (e.g.,
1.2.3.4\texample.com). - Resolver: An external DNS server used to look up authoritative IP addresses for a given hostname.
- Backup File: A temporary copy of the hosts file created in the tool's executable directory before each modification, used to restore the original if the write fails. Named with a date stamp and random string to handle concurrent runs (e.g.,
hosts.bak.20260303-a7f3). Deleted after a successful write.
Assumptions
- The tool runs with sufficient permissions to read and write the hosts file (typically requires administrator/root privileges). Permission errors are reported clearly but obtaining privileges is the user's responsibility.
- The backup file is a temporary rollback mechanism, not a persistent archive. It is stored in the same directory as the tool's binary executable (not alongside the hosts file) and is deleted after a successful write. The filename uses a date stamp plus a short random string to avoid collisions when multiple instances run concurrently.
- Atomic write via temp-file-and-rename combined with a pre-write backup provides two layers of safety. The atomic rename ensures the hosts file is never in a partially-written state. The backup in the executable's directory is a belt-and-suspenders safety net for edge cases where the rename itself fails (e.g., Windows file locking). On success, the backup is deleted. On rename failure, the original hosts file was never touched; the temp file and backup are cleaned up.
- "Go 1.20" is the maximum version to target for legacy system compatibility. No features exclusive to Go 1.21+ will be used.
- The tool will continue to be a single-binary CLI application with no external configuration files or services.
- DNS resolution timeout will use a reasonable default (5 seconds) since no timeout is currently configured.
- Concurrent access is serialized via a lock file in the executable's directory. DNS resolution is performed before acquiring the lock to minimize lock hold time. The lock covers only the file read-modify-write-rename cycle. If a stale lock file is detected (e.g., from a crashed instance), the tool may need a timeout-based expiry or PID check to recover — the specific staleness strategy will be determined during planning.
- The
github.com/miekg/dnsdependency is the only unapproved external package. Its usage surface is narrow (build query, send over UDP, parse A/CNAME response). Replacement will usegolang.org/x/net/dns/dnsmessage(an approved source per the project constitution) which provides low-level DNS message construction and parsing sufficient for this use case.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: The hosts file is never left empty, truncated, or partially written — the file transitions directly from old content to new content via atomic rename. Verified by kill-during-write testing.
- SC-002: CNAME-chain resolution produces zero duplicate IP entries in the hosts file. Verified by testing with hostnames that resolve through CNAME records.
- SC-003: Deleting one hostname from a managed block with multiple hostnames removes exactly the targeted hostname's entries and preserves all others. Verified with multi-host test scenarios.
- SC-004: Every invalid input combination (missing host, missing server, empty values, unknown subcommand) produces a descriptive error message and a non-zero exit code without modifying the hosts file.
- SC-005: All platform-specific functions (Windows, Linux, macOS) exhibit identical error-reporting behavior — returning errors to the caller rather than terminating directly.
- SC-006: The project compiles cleanly with Go 1.20 with no dead code, no unused variables, no deprecated API usage, and zero unapproved external dependencies.
- SC-007: On write failure, the original hosts file content is restored from the temporary backup. On write success, the temporary backup is deleted and no residual backup files remain.
Clarifications
Session 2026-03-03
- Q: How should the tool handle partial DNS resolution failure across multiple hostnames? → A: Write entries for successfully resolved hostnames; print warning to stdout identifying each failed hostname; exit with non-zero code.
- Q: How many backup copies of the hosts file should the tool retain? → A: None persistently. The backup is a temporary rollback file stored in the executable's directory, named with date + random string (e.g.,
hosts.bak.20260303-a7f3). Deleted on successful write; used to restore then deleted on failed write. - Q: Should the tool use atomic rename, backup-and-restore, or both? → A: Both (Option C). Write backup to exe directory, write new content to temp file in hosts file directory, rename temp over original. On success delete backup. On rename failure original was never touched; clean up temp file and backup.
- Q: Should the tool use file locking to prevent concurrent modification of the hosts file? → A: Yes (Option A). Use a lock file in the executable's directory. DNS resolution happens before lock acquisition so the lock is held only during the file read-modify-write cycle. Wait briefly then fail if lock unavailable.