# Task Recon: Implement default tenant management and config directory initialization

**Task ID:** 4.3
**Status:** pending
**Generated:** 2026-01-28T21:45:00Z
**Agent:** claude-opus-4-5-20251101

---

## Task Summary

Implement `get_default_tenant()`, `set_default_tenant()`, and enhance `ensure_config_dir()` methods for managing the default tenant and configuration directory structure with proper permission error handling.

### Parent Task Context

**Parent Task:** Implement ConfigManager (ID: 4)
**Parent Goal:** Create the ConfigManager infrastructure component for reading/writing TOML configuration and managing tenant profiles. The ConfigManager is the foundational component for multi-tenant CLI configuration management.
**This Subtask's Role:** Completes the final methods of ConfigManager - default tenant getter/setter and robust directory initialization with permission error handling. This enables the CLI to automatically select a tenant when none is specified and to gracefully handle filesystem permission issues.

### Dependencies
- Depends on: 4.1 (TOML loading/saving), 4.2 (Tenant CRUD operations)

### Acceptance Criteria
From task details:
1. `get_default_tenant() -> str | None` - loads config and returns `default_tenant` value
2. `set_default_tenant(name: str)` - loads config, validates tenant exists (raises `NotFoundError` if not), sets `default_tenant`, and saves
3. `ensure_config_dir()` - creates parent directory using `Path.mkdir(parents=True, exist_ok=True)`. Already implemented but needs permission error handling
4. Handle permission errors by raising `ConfigurationError` with remediation guidance
5. Call `ensure_config_dir()` from `save_config()` before writing (already done)

---

## Insights from Previous Tasks

### Dependency Task Notes
| Task ID | Title | Key Insight |
|---------|-------|-------------|
| 4.1 | TOML loading/saving | Conditional `tomllib`/`tomli` import pattern; `exclude_none=True` for TOML serialization |
| 4.2 | Tenant CRUD operations | Load-modify-save pattern for atomic operations; `NotFoundError` with remediation messages |

### Reusable Patterns from Dependencies
- **Load-modify-save pattern**: Implemented in `config_manager.py:128-145` during Task 4.2 - load config, modify, save atomically
- **NotFoundError with remediation**: See `config_manager.py:122-125` - pattern for user-friendly error messages
- **Test exception pattern**: Use `pytest.raises(ExceptionClass) as exc_info`, assert on `str(exc_info.value)` and `exc_info.value.remediation`

### Warnings and Gotchas
- **From Task 4.1**: tomlkit throws `ConvertError` on None values - always use `exclude_none=True`
- **From Task 4.2**: `load_config()` returns default empty Config if file doesn't exist, so operations fail naturally for empty configs
- **From Task 4.2**: Run `ruff format` after editing test files

### Open Items Inherited
- [x] Subtask 4.2: Implement tenant CRUD methods (completed)
- [ ] Subtask 4.3: Implement default tenant methods (this task)

---

## Architecture Context

### Relevant Design Decisions
- **ConfigManager is infrastructure layer**: Lowest level component with no dependencies on other infrastructure (per architecture/5-components.md)
- **Exception hierarchy**: All errors inherit from `TeamsPhoneError` with specific exit codes. `ConfigurationError` has exit code 7 (per architecture/11-error-handling-strategy.md)
- **Remediation guidance required**: All user-facing errors must include remediation messages

### Module Structure
```
src/teams_phone/
├── infrastructure/
│   ├── __init__.py          # Exports ConfigManager
│   └── config_manager.py    # ConfigManager class (target file)
├── models/
│   └── config.py            # Config model with default_tenant field
└── exceptions.py            # ConfigurationError, NotFoundError
```

### Conventions to Follow
- Type hints with `str | None` union syntax (Python 3.10+)
- Docstrings with Args, Returns, Raises sections
- `@pytest.mark.unit` marker on all unit tests
- Test class naming: `TestMethodName`

---

## Related Code References

### Primary References (Must Read)
| File | Lines | Purpose |
|------|-------|---------|
| `src/teams_phone/infrastructure/config_manager.py` | 40-46 | Existing `ensure_config_dir()` - needs permission error handling |
| `src/teams_phone/infrastructure/config_manager.py` | 108-169 | `get_tenant`, `set_tenant`, `remove_tenant` patterns to follow |
| `src/teams_phone/models/config.py` | 65 | `default_tenant: Optional[str] = None` field definition |
| `src/teams_phone/exceptions.py` | 112-122 | `ConfigurationError` class definition |

### Secondary References (Helpful Context)
| File | Lines | Purpose |
|------|-------|---------|
| `src/teams_phone/exceptions.py` | 72-82 | `NotFoundError` class for tenant validation |
| `tests/unit/test_config_manager.py` | 453-573 | `TestRemoveTenant` tests showing default_tenant handling patterns |
| `tests/unit/test_config_manager.py` | 42-63 | `TestEnsureConfigDir` - existing tests to extend |

### Code Patterns to Follow

```python
# Pattern 1: get_default_tenant - simple load and return (config_manager.py style)
def get_default_tenant(self) -> str | None:
    """Retrieve the default tenant name.

    Returns:
        The name of the default tenant, or None if not set.
    """
    config = self.load_config()
    return config.default_tenant
```

```python
# Pattern 2: set_default_tenant - validate then update (from set_tenant:128-145)
def set_default_tenant(self, name: str) -> None:
    """Set the default tenant.

    Args:
        name: The tenant name to set as default.

    Raises:
        NotFoundError: If the specified tenant doesn't exist.
    """
    config = self.load_config()
    if name not in config.tenants:
        raise NotFoundError(
            f"Tenant '{name}' not found",
            remediation="Run 'teams-phone tenants list' to see configured tenants.",
        )
    config.default_tenant = name
    self.save_config(config)
```

```python
# Pattern 3: Permission error handling (new pattern to implement)
def ensure_config_dir(self) -> None:
    """Create the configuration directory if it doesn't exist.

    Creates all parent directories as needed.

    Raises:
        ConfigurationError: If directory creation fails due to permission error.
    """
    try:
        self.config_path.parent.mkdir(parents=True, exist_ok=True)
    except PermissionError as e:
        raise ConfigurationError(
            f"Cannot create configuration directory {self.config_path.parent}: {e}",
            remediation=(
                f"Check that you have write permissions to {self.config_path.parent}.\n"
                "You may need to create the directory manually or run with elevated privileges."
            ),
        ) from e
```

---

## Dependencies

### Internal Modules
- `teams_phone.models.Config` - Main config model with `default_tenant` field
- `teams_phone.exceptions.ConfigurationError` - For permission errors
- `teams_phone.exceptions.NotFoundError` - For tenant validation

### External Packages
- `pathlib.Path` - For `mkdir()` with permission handling

### New Dependencies Needed
None required - all necessary imports already present in `config_manager.py`

---

## Testing Guidance

### Test File Location
`tests/unit/test_config_manager.py`

### Test Patterns in Use
```python
# Example test structure (tests/unit/test_config_manager.py:490)
@pytest.mark.unit
def test_clears_default_tenant_if_removed(self, tmp_path: Path) -> None:
    """remove_tenant clears default_tenant if the removed tenant was default."""
    config_path = tmp_path / "config.toml"
    manager = ConfigManager(config_path=str(config_path))
    # ... setup, action, assertion
```

### Fixtures Available
- `tmp_path` (pytest built-in) - Temporary directory for test config files

### Test Utilities
- `tests/conftest.py` - Defines `unit`, `integration`, `contract` markers

### Tests to Implement
From task `testStrategy`:
1. `test_get_default_tenant_returns_none_for_empty_config`
2. `test_get_default_tenant_returns_configured_default`
3. `test_set_default_tenant_updates_default_value`
4. `test_set_default_tenant_raises_not_found_for_nonexistent_tenant`
5. `test_ensure_config_dir_creates_nested_directories` (exists, may need verification)
6. `test_ensure_config_dir_handles_existing_directory` (exists as `test_idempotent`)
7. `test_permission_errors_wrapped_in_configuration_error`

---

## Risks and Blockers

### Technical Risks
- [ ] **PermissionError simulation in tests**: Need to mock `Path.mkdir` to raise `PermissionError` for testing. Consider using `unittest.mock.patch` or `pytest-mock`.

### Dependency Risks
- None - Task 4.1 and 4.2 are complete

### Open Questions
- None identified - task details are clear

---

## Implementation Recommendations

### Suggested Approach
1. Add `get_default_tenant()` method after `remove_tenant()` (line ~170)
2. Add `set_default_tenant()` method after `get_default_tenant()`
3. Modify `ensure_config_dir()` to wrap `PermissionError` in `ConfigurationError`
4. Add test class `TestGetDefaultTenant` with 2 tests
5. Add test class `TestSetDefaultTenant` with 2 tests
6. Add permission error test to `TestEnsureConfigDir`

### Files to Create/Modify
| Action | File | Reason |
|--------|------|--------|
| Modify | `src/teams_phone/infrastructure/config_manager.py` | Add 2 new methods, update 1 existing |
| Modify | `tests/unit/test_config_manager.py` | Add ~7 new tests |

### Quick Wins
- `get_default_tenant()` is trivial: 3 lines of code, validates pattern understanding
- Existing `ensure_config_dir()` just needs try/except wrapper

---

## Cheat Sheet

### Key Imports
```python
# Already in config_manager.py
from teams_phone.exceptions import ConfigurationError, NotFoundError, ValidationError
from teams_phone.models import Config, TenantProfile
```

### Key Functions to Use
- `self.load_config()` - Returns Config object (or default if file missing)
- `self.save_config(config)` - Serializes and writes Config to TOML file
- `self.config_path.parent.mkdir(parents=True, exist_ok=True)` - Create directory

### Common Patterns
- Load-modify-save: See `set_tenant()` at `config_manager.py:128`
- NotFoundError with remediation: See `get_tenant()` at `config_manager.py:122`
- Permission error wrapping: `try: ... except PermissionError as e: raise ConfigurationError(...) from e`

### Test Mocking for PermissionError
```python
from unittest.mock import patch

@pytest.mark.unit
def test_permission_error_raises_configuration_error(self, tmp_path: Path) -> None:
    """ensure_config_dir raises ConfigurationError on permission failure."""
    config_path = tmp_path / "config.toml"
    manager = ConfigManager(config_path=str(config_path))

    with patch.object(Path, "mkdir", side_effect=PermissionError("Access denied")):
        with pytest.raises(ConfigurationError) as exc_info:
            manager.ensure_config_dir()

    assert "permission" in exc_info.value.remediation.lower()
```

---

*Generated by task-recon (claude-opus-4-5-20251101) | Ready for implementation*
