# Task Recon: Implement Constants and Exception Hierarchy

**Task ID:** 2
**Status:** pending
**Generated:** 2026-01-28T22:30:00Z
**Agent:** claude-sonnet-4-5-20250929

---

## Task Summary

Create two foundational modules for the Teams Phone CLI: `constants.py` with API URLs, exit codes, and default configuration values, and `exceptions.py` with a custom exception hierarchy that maps to exit codes and supports remediation messages.

### Dependencies
- Depends on: Task 1 (Initialize Python Project Structure) - COMPLETED

### Acceptance Criteria
1. `src/teams_phone/constants.py` exists with:
   - Graph API base URLs (beta and v1.0)
   - Default file paths for config, cache, and tokens
   - Timeout and retry settings
   - ExitCode enum with integer values 0-8
2. `src/teams_phone/exceptions.py` exists with:
   - TeamsPhoneError base exception class
   - 7 specific exception subclasses (AuthenticationError, AuthorizationError, NotFoundError, ValidationError, RateLimitError, ConfigurationError, ContractError)
   - Each exception has an `exit_code` property
   - Support for optional remediation messages
3. Unit tests in `tests/unit/test_exceptions.py` verify:
   - Each exception maps to correct exit code
   - Remediation message handling works
   - Inheritance hierarchy is correct

---

## Insights from Previous Tasks

### Dependency Task Notes
| Task ID | Title | Key Insight |
|---------|-------|-------------|
| 1 | Initialize Python Project Structure | Project structure created successfully. All stub files exist. Mypy strict mode enabled - all functions need type hints. |

### Reusable Patterns from Dependencies
- **Docstring pattern**: All modules have triple-quoted docstrings at the top (see `src/teams_phone/__init__.py:1`)
- **Type hints**: Mypy strict mode requires all functions to have complete type annotations
- **Import style**: Use absolute imports from `teams_phone` package

### Warnings and Gotchas
- **Mypy strict mode**: All functions need type hints - mypy will fail on untyped code
- **Pre-commit hooks**: Ruff and mypy run automatically on commit - code must pass before commit succeeds
- **README.md reference**: If you add `readme = "README.md"` back to pyproject.toml, the file must exist or build will fail (currently omitted)

### Open Items Inherited
- None directly affect this task

---

## Architecture Context

### Relevant Design Decisions

**Exit Code Strategy (Architecture Section 11.2):**
- Exit codes 0-8 map specific error categories to CLI exit codes
- Exit code 8 (ContractError) is defined in architecture but NOT in CLI spec or PRD appendix - this is intentional for API schema validation failures
- Every exception must map to exactly one exit code for consistent error handling

**Exception Hierarchy (Architecture Section 11.1):**
- Base exception `TeamsPhoneError` provides common interface
- 7 specialized exceptions inherit from base
- Each exception category has specific subclasses (e.g., `AuthenticationError` has `TokenExpiredError`, `InvalidCredentialsError`, `CertificateError`)
- This task implements the TOP LEVEL of the hierarchy only - subclasses will be added in future tasks

**Error Message Format (Architecture Section 11.4):**
- All user-facing errors must include remediation guidance
- Format: Error description + "To fix:" steps + optional config hint
- Remediation messages are OPTIONAL on exceptions (may be added by CLI layer)

**Retry Policy (Architecture Section 11.3):**
- Retry constants (MAX_RETRIES=3, DEFAULT_TIMEOUT=30) defined in constants.py
- Exception hierarchy supports retry decisions (e.g., RateLimitError triggers retry)

### Module Structure
Per architecture section 9 (Source Tree):
- `src/teams_phone/constants.py` - API URLs, exit codes, defaults
- `src/teams_phone/exceptions.py` - Custom exception hierarchy
- Tests mirror source: `tests/unit/test_exceptions.py`

### Conventions to Follow
- **Language:** Python 3.10+ with modern syntax (Architecture 12.1)
- **Type Checking:** Mypy strict mode - all functions need type hints (Architecture 12.1)
- **Linting:** Ruff configured with line-length=88, target-version py310 (Architecture 12.1)
- **No logging of secrets:** Use `[REDACTED]` placeholder for tokens/credentials (Architecture 12.2)
- **Import style:** Use `from enum import IntEnum` for exit codes (standard library)

---

## Related Code References

### Primary References (Must Read)
| File | Lines | Purpose |
|------|-------|---------|
| `C:\Users\frank\Projects\teams-phone\.taskmaster\docs\architecture\11-error-handling-strategy.md` | 1-72 | Complete exception hierarchy specification with exit codes and error message format examples |
| `C:\Users\frank\Projects\teams-phone\.taskmaster\docs\architecture\6-external-apis.md` | 3-34 | Graph API base URLs and endpoint structure (defines GRAPH_API_BASE constants) |
| `C:\Users\frank\Projects\teams-phone\.taskmaster\docs\cli-specification\error-handling.md` | 1-69 | Exit code definitions and Graph API error mapping strategy |
| `C:\Users\frank\Projects\teams-phone\pyproject.toml` | 40-44 | Mypy strict mode configuration - all code must satisfy type checker |

### Secondary References (Helpful Context)
| File | Lines | Purpose |
|------|-------|---------|
| `C:\Users\frank\Projects\teams-phone\src\teams_phone\__init__.py` | 1-2 | Docstring pattern and version constant example |
| `C:\Users\frank\Projects\teams-phone\tests\conftest.py` | 1-11 | Pytest configuration pattern with type hints and marker registration |
| `C:\Users\frank\Projects\teams-phone\.taskmaster\docs\prd\appendix-a-exit-code-reference.md` | 1-15 | User-facing exit code documentation (excludes exit code 8) |

### Code Patterns to Follow

**Docstring Pattern (from `src/teams_phone/__init__.py:1`):**
```python
"""Module description in triple quotes."""
```

**Type Hints Pattern (from `tests/conftest.py:6`):**
```python
def pytest_configure(config: pytest.Config) -> None:
    """Register custom markers for test categorization."""
```

**IntEnum Pattern (from Python standard library - recommended by architecture):**
```python
from enum import IntEnum

class ExitCode(IntEnum):
    """CLI exit codes for error handling."""
    SUCCESS = 0
    GENERAL_ERROR = 1
    # ... etc
```

---

## Dependencies

### Internal Modules
- None required - this is a foundational module that others will import

### External Packages
- `enum.IntEnum` - Standard library (Python 3.10+) for integer enumeration
- No third-party packages needed for this task

### New Dependencies Needed
- None required

---

## Testing Guidance

### Test File Location
`C:\Users\frank\Projects\teams-phone\tests\unit\test_exceptions.py` (to be created)

### Test Patterns in Use

**Test Structure Pattern (from Typer testing best practices):**
```python
import pytest
from teams_phone.exceptions import (
    TeamsPhoneError,
    AuthenticationError,
    ValidationError,
    # ... etc
)
from teams_phone.constants import ExitCode

@pytest.mark.unit
def test_exception_exit_code() -> None:
    """Verify exception maps to correct exit code."""
    exc = AuthenticationError("Token expired")
    assert exc.exit_code == ExitCode.AUTH_ERROR
```

**Remediation Message Pattern:**
```python
@pytest.mark.unit
def test_exception_with_remediation() -> None:
    """Verify exception can include remediation message."""
    remediation = "Run: teams-phone auth login"
    exc = AuthenticationError("Token expired", remediation=remediation)
    assert remediation in str(exc)
```

**Inheritance Pattern:**
```python
@pytest.mark.unit
def test_exception_inheritance() -> None:
    """Verify exception inherits from base class."""
    exc = ValidationError("Invalid input")
    assert isinstance(exc, TeamsPhoneError)
    assert isinstance(exc, Exception)
```

### Fixtures Available
- `tests/conftest.py` - Pytest markers configured (unit, integration, contract)
- No fixtures needed for this task - pure unit tests

### Test Utilities
- `pytest.mark.unit` - Marker for fast unit tests (configured in `pyproject.toml:49` and `tests/conftest.py:8`)
- `pytest.raises` - Standard pytest utility for testing exception raising

---

## Risks and Blockers

### Technical Risks
- [X] **Exit code 8 inconsistency**: Architecture defines ContractError with exit 8, but CLI spec and PRD appendix only document codes 0-7. This is intentional - exit code 8 is for internal schema validation failures and not expected in normal user operations. MITIGATION: Implement as specified in architecture, document clearly in code comments.
- [ ] **IntEnum vs Enum choice**: Task spec uses `IntEnum` which allows integer comparison but breaks enum semantic isolation. This is acceptable here because exit codes MUST be integers for shell compatibility. MITIGATION: Use IntEnum as specified, document rationale in docstring.

### Dependency Risks
- [X] **Task 1 completion**: Project structure must exist. VERIFIED: Task 1 is marked "done" and stub files exist.
- [ ] **Python version**: Requires Python 3.10+ for modern syntax. MITIGATION: Specified in `pyproject.toml:5` - uv enforces this.

### Open Questions
- [ ] **Subclass exceptions now or later?** Architecture section 11.1 shows subclasses like `TokenExpiredError`, `InvalidCredentialsError`, etc. Task details say implement TOP LEVEL only (7 exceptions). CLARIFICATION: Implement only the 7 main exception classes (AuthenticationError, etc.) - subclasses will be added when needed by specific features.
- [ ] **Remediation message format?** Task says "remediation message support" but doesn't specify if remediation is a separate field or part of message string. RECOMMENDATION: Add optional `remediation: str | None = None` parameter to base exception, format in `__str__` method following architecture 11.4 example.
- [ ] **Exception message formatting?** Should exceptions auto-format with "✗ Error:" prefix or is that CLI layer responsibility? RECOMMENDATION: Keep exceptions simple - CLI layer (Typer) handles formatting with Rich library for colored output.

---

## Implementation Recommendations

### Suggested Approach
1. **Create constants.py first** (simpler, no dependencies):
   - Add module docstring
   - Define string constants for API URLs and file paths
   - Define integer constants for timeouts and retries
   - Define ExitCode IntEnum with values 0-8
   - Add inline comments explaining exit code 8 inconsistency

2. **Create exceptions.py second** (imports ExitCode):
   - Add module docstring
   - Define TeamsPhoneError base class with exit_code property and optional remediation parameter
   - Define 7 exception subclasses, each setting appropriate exit_code
   - Use type hints for all methods (strict mypy requirement)
   - Keep messages simple - no CLI formatting here

3. **Create test_exceptions.py last** (validates both modules):
   - Add unit marker to all tests
   - Test each exception's exit_code property
   - Test remediation message handling
   - Test inheritance chain
   - Run with: `uv run pytest tests/unit/test_exceptions.py -v`

### Files to Create/Modify
| Action | File | Reason |
|--------|------|--------|
| Modify | `C:\Users\frank\Projects\teams-phone\src\teams_phone\constants.py` | Add constants and ExitCode enum (currently stub) |
| Modify | `C:\Users\frank\Projects\teams-phone\src\teams_phone\exceptions.py` | Add exception hierarchy (currently stub) |
| Create | `C:\Users\frank\Projects\teams-phone\tests\unit\test_exceptions.py` | Unit tests for exceptions |

### Quick Wins
- Start with ExitCode enum in constants.py - easy to validate with a simple Python REPL check: `from teams_phone.constants import ExitCode; print(ExitCode.AUTH_ERROR == 2)`
- Validate mypy compliance incrementally: `uv run mypy src/teams_phone/constants.py` before moving to exceptions.py

---

## Cheat Sheet

### Key Imports
```python
# For constants.py
from enum import IntEnum

# For exceptions.py
from teams_phone.constants import ExitCode

# For tests
import pytest
from teams_phone.exceptions import TeamsPhoneError, AuthenticationError, ...
from teams_phone.constants import ExitCode
```

### Key Constants to Define
- `GRAPH_API_BASE = "https://graph.microsoft.com/beta"`
- `GRAPH_API_V1_BASE = "https://graph.microsoft.com/v1.0"`
- `DEFAULT_CONFIG_PATH = "~/.teams-phone/config.toml"`
- `DEFAULT_CACHE_PATH = "~/.teams-phone/cache"`
- `DEFAULT_TOKEN_PATH = "~/.teams-phone/tokens"`
- `CSV_STALE_DAYS = 30`
- `DEFAULT_TIMEOUT = 30`
- `MAX_RETRIES = 3`

### Exit Code Mapping
| Code | Exception | Usage |
|------|-----------|-------|
| 0 | N/A | Success (no exception) |
| 1 | TeamsPhoneError | General/unexpected errors |
| 2 | AuthenticationError | Token expired, invalid credentials |
| 3 | AuthorizationError | Missing Graph API permissions |
| 4 | NotFoundError | User/number/policy not found |
| 5 | ValidationError | Invalid input, E911 violations |
| 6 | RateLimitError | Graph API throttling |
| 7 | ConfigurationError | Invalid config file, missing tenant |
| 8 | ContractError | API schema validation failures |

### Exception Hierarchy Pattern
```python
class TeamsPhoneError(Exception):
    """Base exception for all Teams Phone CLI errors."""
    exit_code: int = ExitCode.GENERAL_ERROR

    def __init__(self, message: str, remediation: str | None = None) -> None:
        self.message = message
        self.remediation = remediation
        super().__init__(message)

class AuthenticationError(TeamsPhoneError):
    """Authentication failures (exit code 2)."""
    exit_code: int = ExitCode.AUTH_ERROR
```

### Test Assertion Patterns
```python
# Test exit code
assert exc.exit_code == ExitCode.AUTH_ERROR
assert exc.exit_code == 2  # Also valid since IntEnum

# Test inheritance
assert isinstance(exc, TeamsPhoneError)

# Test message
assert "Token expired" in str(exc)
```

---

*Generated by task-recon (claude-sonnet-4-5-20250929) | Ready for implementation*
