# Task ID: 25

**Title:** Implement Policies CLI Commands

**Status:** pending

**Dependencies:** 8, 11, 17, 24

**Priority:** medium

**Description:** Create the policies command group with list, show, and assign commands.

**Details:**

Create `src/teams_phone/cli/policies.py`:
```python
policies_app = typer.Typer(name="policies", help="Voice policy management")

@policies_app.command("list")
def list_policies(
    ctx: typer.Context,
    policy_type: Optional[str] = typer.Option(None, "--type", help="Filter by policy type"),
):
    """List available voice policies from CSV cache."""
    # Show staleness warning if applicable
    # Display: policyType, policyName, description, isGlobal
    ...

@policies_app.command()
def show(ctx: typer.Context, user: str = typer.Argument(..., help="User UPN, name, or ID")):
    """Show policy assignments for a user."""
    # Display: policyType, policyName, assignmentType (direct/group/global)
    ...

@policies_app.command()
def assign(
    ctx: typer.Context,
    user: str = typer.Argument(...),
    policy_type: str = typer.Argument(..., help="Policy type"),
    policy_name: str = typer.Argument(..., help="Policy name or 'Global'"),
    dry_run: bool = typer.Option(False, "--dry-run"),
):
    """Assign a policy to a user."""
    ...
```

**Test Strategy:**

CLI tests: list with type filter, show displays assignment sources, assign with validation, dry-run preview, JSON output.

---

## Implementation Notes

### Session: 2026-01-29 21:45 - Agent: claude-sonnet-4-5-20250929

**Status:** Implemented

#### What Was Done
- Created `src/teams_phone/cli/policies.py` with three commands: `list`, `show`, `assign`
- Updated `src/teams_phone/cli/main.py` to import `policies_app` from the new module (removed placeholder)
- Created `tests/unit/test_cli_policies.py` with 33 unit tests covering all commands

#### Key Decisions Made
- **Policy type mapping**: Implemented `POLICY_TYPE_MAP` dict to map CLI aliases (e.g., `calling`) to API names (e.g., `TeamsCallingPolicy`). This allows users to use short names while maintaining compatibility with the Graph API.
- **Sync service for list**: Created `_get_sync_service()` helper that uses a MagicMock for the GraphClient since `list_policies` only accesses the CSV cache via CacheManager. This avoids unnecessary authentication for cache-only operations.
- **Assignment type formatting**: Display "Direct", "Group", or "Global (Default)" instead of raw API values for better readability.
- **Two-phase assign**: The assign command first resolves the user (async), validates the policy exists (sync from cache), then performs the assignment (async). This allows dry-run to work without Graph API access for the assignment itself.

#### Patterns Discovered
- **CLI alias to API name mapping**: See `policies.py:35-42` - useful for any future CLI commands that need user-friendly aliases
- **Sync service pattern**: See `policies.py:122-131` - for commands that only need cache access, avoiding full async setup

#### Open Items for Next Agent
- [ ] Task 26: Implement `policies bulk-assign` command for CSV-based bulk policy assignments
- [ ] Consider adding `--show-assignments` flag to list command (would require cross-referencing with user data)

#### Gotchas and Warnings
- **TeamsVoicemailPolicy not supported**: The Graph API batch assignment endpoint doesn't support voicemail policies. The PolicyService raises `ValidationError` with remediation pointing to PowerShell cmdlet.
- **Policy type argument position**: In the CLI spec, `list` takes policy type as a positional argument (e.g., `policies list calling`), not as `--type` option. Implementation follows this pattern.
- **Import order**: Import from `teams_phone.infrastructure` before services to avoid circular imports (noted in task-recon but not an issue in practice).

---
