torrust-tracker-deployer 0.1.0

Torrust Tracker Deployer - Deployment Infrastructure with Ansible and OpenTofu
Documentation
# Decision Record: Actionable Error Messages

**Date**: October 3, 2025  
**Status**: ✅ Accepted  
**Context**: Implementing Proposal #8 - Improve Error Context  
**Decision Makers**: Development Team

## 📋 Problem Statement

When implementing Proposal #8 to improve error messages in the file lock module, we faced a design challenge: how to make errors actionable and helpful without overwhelming users with verbose messages or requiring external infrastructure.

### Requirements

1. **Actionability**: Errors must guide users toward solutions (development principle)
2. **Brevity**: Error messages shouldn't be overwhelming or cluttered
3. **Accessibility**: Help must be available when users need it
4. **Maintainability**: Solution should be easy to maintain and update
5. **No External Dependencies**: Avoid requiring web hosting or internet access
6. **Runtime Availability**: Help must be accessible at runtime, not just in documentation

### Initial Proposal

The initial Proposal #8 suggested embedding extensive troubleshooting steps directly in error messages:

```rust
#[error("Failed to acquire lock for '{path}' within {timeout:?}

The lock is currently held by process {holder_pid}.

To resolve this issue:
1. Check if process {holder_pid} is still running:
   ps -p {holder_pid}

2. If the process is running and should release the lock:
   - Wait for the process to complete its operation
   - Or increase the timeout duration

3. If the process is stuck or hung:
   - Terminate it: kill {holder_pid}
   - Or force terminate: kill -9 {holder_pid}

4. If the process doesn't exist (stale lock):
   - This should be handled automatically
   - If you see this error repeatedly, report a bug

Current timeout: {timeout:?}
Lock file: {path}.lock")]
```

**Problems identified:**

- ❌ Too verbose - error messages become documentation
- ❌ Maintenance burden - commands may become outdated
- ❌ Not DRY - duplicates publicly available information
- ❌ Clutters logs and terminal output

## 🔍 Alternatives Considered

### Option 1: Error Codes + External Web Documentation

**Approach**: Assign error codes (like Rust's E0001) and link to web documentation.

```rust
#[error("Failed to acquire lock for '{path}' (error code: FL001)
See: https://docs.torrust.com/errors/FL001 for troubleshooting")]
```

**Pros:**

- ✅ Clean separation of concerns
- ✅ Detailed help without cluttering errors
- ✅ Easy to update documentation independently
- ✅ Similar to Rust's own error codes

**Cons:**

- **Stability problem**: Internal errors change frequently, making code numbering unstable
- ❌ Requires infrastructure (hosting docs, numbering scheme)
- ❌ Users need internet access to see help
- ❌ Extra cognitive load (see error → look up code → read docs)
- ❌ Version synchronization issues between binary and docs

**Decision**: ❌ Rejected due to stability concerns and infrastructure requirements.

---

### Option 2: Rustdoc Only

**Approach**: Document errors in Rustdoc, accessible via `cargo doc`.

```rust
/// Failed to acquire lock within timeout period
///
/// ## Troubleshooting
///
/// 1. Check if process is running: ps -p <pid>
/// 2. Wait or increase timeout
/// 3. Terminate stuck processes
#[error("Failed to acquire lock for '{path}'")]
AcquisitionTimeout { /* ... */ }
```

**Pros:**

- ✅ Documentation lives with the code
- ✅ Easy to maintain
- ✅ Accessible via `cargo doc`

**Cons:**

- **Target audience mismatch**: Rustdoc is for developers, not end-users
- ❌ Not accessible at runtime when users need it
- ❌ Users running binaries won't have access to docs
- ❌ Doesn't help with production issues

**Decision**: ❌ Rejected because help isn't available when users need it most (at runtime).

---

### Option 3: Brief Tips Only

**Approach**: Include only one-line hints in error messages.

```rust
#[error("Failed to acquire lock for '{path}' (held by process {holder_pid})
Tip: Check if process is running or increase timeout")]
```

**Pros:**

- ✅ Balances brevity with actionability
- ✅ No external infrastructure
- ✅ Aligns with development principles

**Cons:**

- ❌ Limited guidance for complex scenarios
- ❌ Users may still struggle without detailed steps
- ❌ Platform-specific commands not easily conveyed

**Decision**: ⚠️ Good but insufficient on its own - incorporated into final solution.

---

### Option 4: External Tools/Generic References

**Approach**: Point to existing tools and generic documentation.

```rust
#[error("Failed to acquire lock for '{path}' (held by process {holder_pid})
Check process status with: ps -p {holder_pid}
See: https://docs.torrust.com/troubleshooting#file-locks")]
```

**Pros:**

- ✅ Leverages existing documentation
- ✅ Can provide deep troubleshooting
- ✅ Documentation evolves independently

**Cons:**

- ❌ Requires maintaining external docs
- ❌ Requires internet access
- ❌ Link rot over time
- ❌ Version synchronization issues

**Decision**: ❌ Rejected due to external dependency and maintenance burden.

---

### Option 5: Tiered Help System (RECOMMENDED)

**Approach**: Provide concise errors with brief tips, plus an optional `.help()` method for detailed guidance.

```rust
#[derive(Debug, Error)]
pub enum FileLockError {
    /// Failed to acquire lock within timeout period
    ///
    /// Use `.help()` for detailed troubleshooting steps.
    #[error("Failed to acquire lock for '{path}' within {timeout:?} (held by process {holder_pid})
Tip: Use 'ps -p {holder_pid}' to check if process is running")]
    AcquisitionTimeout {
        path: PathBuf,
        holder_pid: ProcessId,
        timeout: Duration,
    },
}

impl FileLockError {
    /// Get detailed troubleshooting guidance
    pub fn help(&self) -> &'static str {
        match self {
            Self::AcquisitionTimeout { .. } => {
                "Lock Acquisition Timeout - Detailed Troubleshooting:

1. Check if holder process is running:
   Unix: ps -p <pid>
   Windows: tasklist /FI \"PID eq <pid>\"

2. If running: wait or increase timeout
3. If stuck: terminate process
4. If stale: report bug

See docs for more info."
            }
        }
    }
}
```

**Usage:**

```rust
// Basic: just show error
eprintln!("Error: {e}");

// Advanced: show help when needed
if verbose {
    eprintln!("\n{}", e.help());
}
```

**Pros:**

- ✅ Clean separation: error vs help
- ✅ No external infrastructure needed
- ✅ Help always available at runtime
- ✅ Users get help when they need it
- ✅ No version stability concerns
- ✅ Easy to maintain (help with error definition)
- ✅ Balances brevity with actionability
- ✅ Platform-specific guidance possible
- ✅ Aligns with all development principles

**Cons:**

- ⚠️ Help strings embedded in binary (small overhead)
- ⚠️ Still some duplication of publicly available info

**Decision**: ✅ **ACCEPTED** - Best balance of all considerations.

---

## 🎯 Final Decision

**We adopt the Tiered Help System approach (Option 5)** combining:

1. **Base error message**: Concise with essential context
2. **Brief tip**: One-liner actionable hint (from Option 3)
3. **`.help()` method**: Detailed troubleshooting on-demand
4. **Rustdoc**: Developer documentation (from Option 2)

### Implementation Pattern

```rust
use thiserror::Error;

#[derive(Debug, Error)]
pub enum FileLockError {
    /// Brief description for developers
    ///
    /// Longer context and when this error occurs.
    /// Use `.help()` for user-facing troubleshooting.
    #[error("Concise error with context
Tip: Brief actionable hint")]
    VariantName {
        // Error fields with #[source] where appropriate
    },
}

impl FileLockError {
    /// Get detailed troubleshooting guidance
    ///
    /// Returns platform-specific steps to resolve the error.
    pub fn help(&self) -> &'static str {
        match self {
            Self::VariantName { .. } => {
                "Detailed troubleshooting with:
1. Step-by-step instructions
2. Platform-specific commands
3. Multiple resolution approaches
4. Links to report bugs"
            }
        }
    }
}
```

## 📊 Comparison Matrix

| Criterion             | Error Codes         | Rustdoc Only | Brief Tips | External Refs | **Tiered Help**      |
| --------------------- | ------------------- | ------------ | ---------- | ------------- | -------------------- |
| **Brevity**           | ✅ Excellent        | ✅ Excellent | ✅ Good    | ✅ Good       | ✅ **Excellent**     |
| **Actionability**     | ✅ High             | ❌ Low       | ⚠️ Medium  | ✅ High       | ✅ **Very High**     |
| **Runtime Access**    | ❌ No               | ❌ No        | ✅ Yes     | ❌ Partial    | ✅ **Yes**           |
| **No Infrastructure** | ❌ Needs web        | ✅ Yes       | ✅ Yes     | ❌ Needs web  | ✅ **Yes**           |
| **Maintainability**   | ⚠️ Medium           | ✅ Easy      | ✅ Easy    | ⚠️ Medium     | ✅ **Easy**          |
| **Stability**         | ❌ Numbering issues | ✅ Stable    | ✅ Stable  | ⚠️ Link rot   | ✅ **Stable**        |
| **User Control**      | ❌ No               | ❌ No        | ❌ No      | ❌ No         | ✅ **Yes (verbose)** |

## 🎬 Implementation Guidelines

### For Error Definitions

1. Add Rustdoc comment explaining when/why error occurs
2. Use `#[error]` with concise message + brief tip
3. Include `#[source]` for underlying errors
4. Implement `.help()` method with detailed guidance

### For Application Code

```rust
// Let users control verbosity
match operation() {
    Ok(result) => { /* success */ }
    Err(e) => {
        eprintln!("Error: {e}");

        if args.verbose {
            eprintln!("\n{}", e.help());
        } else {
            eprintln!("\nRun with --verbose for detailed troubleshooting");
        }
    }
}
```

### For Testing

- Test error messages contain tips
- Test `.help()` returns non-empty strings for all variants
- Test help content includes key troubleshooting terms

## 📚 Related Documentation

- [Error Handling Guide]../contributing/error-handling.md - Full implementation guidance
- [Development Principles]../development-principles.md - Actionability principle
- Proposal #8 implementation details - See git history for `docs/refactors/file-lock-improvements.md` (completed October 3, 2025)

## 🔄 Future Considerations

If we find the tiered help system insufficient, we could:

1. **Generate HTML docs**: Build-time generation from `.help()` content
2. **Error catalog**: Maintain a searchable error database
3. **Telemetry integration**: Track which errors need better help

For now, the tiered help system provides the best balance of simplicity and effectiveness.

---

**Last Updated**: October 3, 2025  
**Review Date**: After Proposal #8 implementation and user feedback