# Task Recon: Implement Pydantic Models for Configuration

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

---

## Task Summary

Create Pydantic models for tenant profiles, configuration schema, and authentication tokens used by ConfigManager. This includes:

1. `src/teams_phone/models/tenant.py` - TenantProfile and AuthMethod enum
2. `src/teams_phone/models/config.py` - Config model with defaults, cache settings, tenant dict
3. `src/teams_phone/models/auth.py` - CachedToken and AuthStatus models

### Dependencies
- Depends on: Task 1 (Initialize Python Project Structure) - **DONE**
- Depends on: Task 2 (Implement Constants and Exception Hierarchy) - **DONE**

### Acceptance Criteria
- Models use Pydantic strict mode with `extra="forbid"` per coding standards
- TenantProfile supports UUID for tenant_id and client_id
- AuthMethod enum with `certificate` and `client-secret` values
- Config model contains defaults, cache settings, and tenant dictionary
- CachedToken and AuthStatus models for authentication state
- Unit tests validating model creation, extra field rejection, serialization/deserialization, and validation errors

---

## Insights from Previous Tasks

### Dependency Task Notes

| Task ID | Title | Key Insight |
|---------|-------|-------------|
| 1 | Initialize Python Project Structure | Pydantic ^2.0.0 installed as dependency; mypy strict mode requires full type hints |
| 2 | Implement Constants and Exception Hierarchy | Use class attributes for shared values; keyword-only optional params (`*, remediation: str | None = None`) |

### Reusable Patterns from Dependencies
- **Docstring pattern**: Google style docstrings established in `src/teams_phone/__init__.py`
- **IntEnum pattern**: See `src/teams_phone/constants.py:52` for `ExitCode(IntEnum)` - use `str, Enum` for string enums
- **Class attribute typing**: Type as `int` not `ExitCode` since IntEnum values are ints; for string Enum subclasses type as `str`

### Warnings and Gotchas
- **Mypy strict mode**: All functions need complete type hints. Class attributes must be typed.
- **README.md reference**: If you add `readme = "README.md"` back to pyproject.toml, the file must exist or build will fail

### Open Items Inherited
- [ ] README.md creation (likely separate documentation task)
- [ ] LICENSE file creation (likely separate task)

---

## Architecture Context

### Relevant Design Decisions
- **Pydantic strict mode mandatory**: Per coding standards (Section 12.3), ALL API response models must use `extra="forbid"` to fail-fast on unknown fields
- **Layered monolith**: Models live in `src/teams_phone/models/` and are used by services and infrastructure layers
- **ConfigManager uses models**: Per architecture doc Section 5.3.3, ConfigManager depends on Pydantic models for tenant profiles

### Module Structure
From architecture doc Section 9 (Source Tree):
```
src/teams_phone/models/
├── __init__.py           # Re-export public models
├── user.py               # UserConfiguration (future)
├── number.py             # NumberAssignment (future)
├── policy.py             # Policy, PolicyAssignment (future)
├── location.py           # EmergencyLocation (future)
├── tenant.py             # TenantProfile, AuthMethod (this task)
├── auth.py               # CachedToken, AuthStatus (this task)
└── api_responses.py      # Graph API responses (future)
```

For this task, create `tenant.py`, `config.py`, and `auth.py`. Update `__init__.py` to re-export.

### Conventions to Follow
- Use `ConfigDict(extra="forbid")` on all models
- Use `ConfigDict(populate_by_name=True)` if API uses camelCase
- Use `Field(alias="camelCase")` for API field mapping
- Use `str, Enum` base class for string enums (not just `Enum`)
- Use `UUID` type from `uuid` module for tenant/client IDs

---

## Related Code References

### Primary References (Must Read)

| File | Lines | Purpose |
|------|-------|---------|
| `src/teams_phone/constants.py` | 52-93 | ExitCode IntEnum pattern to follow for AuthMethod |
| `src/teams_phone/exceptions.py` | 10-44 | TeamsPhoneError class structure with docstrings |
| `src/teams_phone/models/__init__.py` | 1-2 | Current empty init, needs to export new models |

### Secondary References (Helpful Context)

| File | Lines | Purpose |
|------|-------|---------|
| `.taskmaster/docs/architecture/4-data-models.md` | 81-97 | TenantProfile specification from architecture |
| `.taskmaster/docs/architecture/12-coding-standards.md` | 27-41 | Pydantic model standards with example |
| `pyproject.toml` | 11 | Confirms pydantic>=2.0.0 is available |

### Code Patterns to Follow

```python
# Pattern from coding standards (Section 12.3)
from pydantic import BaseModel, Field, ConfigDict

class NumberAssignment(BaseModel):
    model_config = ConfigDict(
        extra="forbid",           # CRITICAL: Fail on unknown fields
        populate_by_name=True,    # Allow both snake_case and camelCase
    )

    id: str
    telephone_number: str = Field(alias="telephoneNumber")
    number_type: NumberType = Field(alias="numberType")
```

```python
# Enum pattern from constants.py:52
from enum import IntEnum

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

# For string enums, use:
from enum import Enum

class AuthMethod(str, Enum):
    """Authentication methods supported for tenant profiles."""
    CERTIFICATE = "certificate"
    CLIENT_SECRET = "client-secret"
```

---

## Dependencies

### Internal Modules
- `teams_phone.constants` - May need DEFAULT_CONFIG_PATH, DEFAULT_CACHE_PATH, DEFAULT_TOKEN_PATH for Config model defaults
- `teams_phone.exceptions` - ConfigurationError for validation failures

### External Packages
- `pydantic` (>=2.0.0) - BaseModel, ConfigDict, Field
- `uuid` (stdlib) - UUID type for tenant_id, client_id
- `datetime` (stdlib) - datetime for token expiration timestamps
- `typing` (stdlib) - Optional, Any if needed

### New Dependencies Needed
- None required - all dependencies already in pyproject.toml

---

## Testing Guidance

### Test File Location
Create `tests/unit/test_models.py` (or split into `test_models_tenant.py`, `test_models_config.py`, `test_models_auth.py`)

### Test Patterns in Use

```python
# Example test structure (tests/unit/test_exceptions.py:18-27)
class TestTeamsPhoneError:
    """Tests for the base TeamsPhoneError class."""

    @pytest.mark.unit
    def test_base_exception_exit_code(self) -> None:
        """Base exception has GENERAL_ERROR exit code."""
        exc = TeamsPhoneError("Something went wrong")
        assert exc.exit_code == ExitCode.GENERAL_ERROR
        assert exc.exit_code == 1
```

### Fixtures Available
- `tests/conftest.py` - Currently only has pytest marker configuration
- Create fixtures for sample tenant profiles, config objects

### Test Utilities
- pytest-mock (available) for mocking
- Test marker: `@pytest.mark.unit`

### Test Cases to Implement
1. **TenantProfile creation** - valid data creates model
2. **TenantProfile extra field rejection** - `extra="forbid"` rejects unknown fields
3. **TenantProfile UUID validation** - tenant_id, client_id accept UUID strings and objects
4. **AuthMethod enum values** - "certificate" and "client-secret" are valid
5. **Config model defaults** - default values work when omitted
6. **Config serialization** - model_dump() produces correct dict
7. **CachedToken expiration** - timestamp handling works
8. **AuthStatus states** - model accepts expected status values

---

## Risks and Blockers

### Technical Risks
- [ ] **UUID serialization**: Pydantic 2.x handles UUID serialization differently than 1.x. May need to verify JSON output uses strings not UUID objects.
- [ ] **Optional field handling**: Ensure `Optional[str] = None` fields serialize correctly in JSON (omit vs null).

### Dependency Risks
- None identified - all dependencies are completed (Tasks 1 and 2 are done)

### Open Questions
- [ ] Should Config model store tenant dict as `dict[str, TenantProfile]` or as `list[TenantProfile]` with name-based lookup? (Architecture doc 4.5 suggests dict keyed by name)
- [ ] Should CachedToken include refresh_token or only access_token? (MSAL caching may handle this separately)
- [ ] Does AuthStatus need tenant-specific status or just global authenticated/unauthenticated?

---

## Implementation Recommendations

### Suggested Approach
1. Create `src/teams_phone/models/tenant.py` with AuthMethod enum and TenantProfile model
2. Create `src/teams_phone/models/auth.py` with CachedToken and AuthStatus models
3. Create `src/teams_phone/models/config.py` with Config model using TenantProfile
4. Update `src/teams_phone/models/__init__.py` to re-export all public types
5. Create `tests/unit/test_models.py` with comprehensive unit tests
6. Run `ruff check`, `ruff format`, and `mypy` to validate code quality

### Files to Create/Modify

| Action | File | Reason |
|--------|------|--------|
| Create | `src/teams_phone/models/tenant.py` | AuthMethod enum and TenantProfile model |
| Create | `src/teams_phone/models/auth.py` | CachedToken and AuthStatus models |
| Create | `src/teams_phone/models/config.py` | Config model with defaults and tenant dict |
| Modify | `src/teams_phone/models/__init__.py` | Re-export AuthMethod, TenantProfile, Config, CachedToken, AuthStatus |
| Create | `tests/unit/test_models.py` | Unit tests for all new models |

### Quick Wins
- Start with AuthMethod enum - simplest, establishes pattern
- Then TenantProfile - clearly specified in task details
- Config and auth models can reference TenantProfile

---

## Cheat Sheet

### Key Imports

```python
from enum import Enum
from typing import Optional
from uuid import UUID
from datetime import datetime

from pydantic import BaseModel, ConfigDict, Field

from teams_phone.constants import DEFAULT_CONFIG_PATH, DEFAULT_CACHE_PATH, DEFAULT_TOKEN_PATH
```

### Key Functions to Use
- `BaseModel.model_dump()` - serialize model to dict
- `BaseModel.model_dump_json()` - serialize to JSON string
- `BaseModel.model_validate()` - create from dict with validation
- `BaseModel.model_validate_json()` - create from JSON string

### Common Patterns
- **String Enum**: `class AuthMethod(str, Enum):` - See `src/teams_phone/constants.py:52`
- **ConfigDict extra forbid**: `model_config = ConfigDict(extra="forbid")`
- **Optional field**: `cert_path: Optional[str] = None`
- **UUID field**: `tenant_id: UUID` - Pydantic handles string-to-UUID coercion

### Model Skeleton (from task details)

```python
from pydantic import BaseModel, ConfigDict, Field
from enum import Enum
from typing import Optional
from uuid import UUID

class AuthMethod(str, Enum):
    CERTIFICATE = "certificate"
    CLIENT_SECRET = "client-secret"

class TenantProfile(BaseModel):
    model_config = ConfigDict(extra="forbid")
    name: str
    tenant_id: UUID
    client_id: UUID
    display_name: str
    auth_method: AuthMethod
    cert_path: Optional[str] = None
    default_location: Optional[str] = None
```

---

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