# Task ID: 37

**Title:** Set Up CI/CD Pipeline

**Status:** pending

**Dependencies:** 33, 34, 35

**Priority:** medium

**Description:** Create GitHub Actions workflows for CI (lint, test), releases, and daily contract tests.

**Details:**

Create `.github/workflows/ci.yml`:
```yaml
name: CI
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync
      - run: uv run ruff check .
      - run: uv run mypy src/
  
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync
      - run: uv run pytest --cov=teams_phone --cov-report=xml
      - uses: codecov/codecov-action@v4
```

Create `contract-tests.yml` for daily scheduled runs.
Create `release.yml` for tag-triggered PyPI publishing.

**Test Strategy:**

Push to branch and verify CI runs successfully. Create test tag to verify release workflow syntax.

---

## Subtask 37.1 Implementation Notes

### Session: 2026-01-30 12:00 - Agent: claude-opus-4-5-20251101

**Status:** Implemented

#### What Was Done
- Created `.github/workflows/ci.yml` with complete CI pipeline
- Added `lint` job: runs `ruff check` and `ruff format --check` on `src/` and `tests/`
- Added `typecheck` job: runs `mypy src/` (strict mode per pyproject.toml)
- Added `test` job with Python version matrix (3.10, 3.11, 3.12)
- Configured job dependencies: `test` job `needs: [lint, typecheck]` for sequential execution
- Added Codecov coverage upload with `codecov-action@v5`
- Added `pytest-cov>=7.0.0` to dev dependencies in pyproject.toml (was installed but not tracked)
- Fixed 24 pre-existing ruff formatting issues to ensure CI passes

#### Key Decisions Made
- **actions/checkout@v5 and astral-sh/setup-uv@v7**: Used latest stable versions (task spec had @v4)
- **codecov-action@v5**: Used v5 as specified in task requirements
- **fail_ci_if_error: false**: Set to false to avoid blocking CI on Codecov upload issues
- **cache-suffix per Python version**: Added `cache-suffix: ${{ matrix.python-version }}` for isolated per-version caching
- **Separate lint and typecheck jobs**: Keeps them parallel for faster feedback
- **Unit tests only**: Used `-m unit` marker to exclude integration/contract tests per task spec

#### Patterns Discovered
- pytest markers configured in `pyproject.toml:55-59` with `unit`, `integration`, `contract`
- mypy strict mode already configured in `pyproject.toml:47-51`
- asyncio_mode = "strict" in pytest config requires explicit `@pytest.mark.asyncio`

#### Open Items for Next Agent
- CODECOV_TOKEN secret must be configured in GitHub repository settings before CI upload works
- Consider running integration tests in CI after full unit test coverage (future enhancement)

#### Gotchas and Warnings
- Python versions must be quoted strings in YAML matrix ("3.10" not 3.10) to avoid YAML float interpretation
- Pre-existing formatting issues required `ruff format` fix before CI can pass
- pytest-cov was installed but not in pyproject.toml dependencies

---

## Subtask 37.2 Implementation Notes

### Session: 2026-01-30 - Agent: claude-opus-4-5-20251101

**Status:** Implemented

#### What Was Done
- Created `.github/workflows/release.yml` with tag-triggered release pipeline
- Added `build` job: validates tag version matches pyproject.toml, runs `uv build`, uploads artifacts
- Added `publish` job: uses PyPI trusted publishing (OIDC) via `pypa/gh-action-pypi-publish@release/v1`
- Added `release` job: creates GitHub Release with auto-generated notes and attached artifacts
- Documented prerequisites (PyPI trusted publisher, GitHub environment) in workflow comments
- Used consistent action versions: `actions/checkout@v5`, `astral-sh/setup-uv@v7` (matching ci.yml)

#### Key Decisions Made
- **Version validation**: Extracts version from tag (strips `v` prefix) and compares to pyproject.toml
- **Trusted publishing**: Uses OIDC authentication - no API tokens required
- **Environment protection**: `environment: pypi` enables optional deployment protection rules
- **Sequential jobs**: `build` → `publish` → `release` with proper `needs:` dependencies
- **Auto-generated release notes**: Uses `generate_release_notes: true` for consistent release notes

#### Patterns Discovered
- Package name: `teams-phone-cli` on PyPI (with dashes), `teams_phone` import name (underscores)
- Version location: Both `pyproject.toml:3` and `src/teams_phone/__init__.py:3` have version "0.1.0"
- Build backend: `hatchling` configured in `pyproject.toml:33-35`

#### Open Items for Next Agent
- PyPI trusted publisher must be configured before first release (see workflow comments)
- GitHub environment "pypi" must be created in repository settings
- Consider TestPyPI testing for pre-release versions
- Consider manual approval requirement for production deployments

#### Gotchas and Warnings
- First release will fail if trusted publisher not configured in PyPI project settings
- Tag version must match pyproject.toml exactly (e.g., `v0.1.0` for version "0.1.0")
- attestations are enabled by default in pypa/gh-action-pypi-publish (may want to disable if issues arise)

---

## Subtask 37.3 Implementation Notes

### Session: 2026-01-30 19:30 - Agent: claude-opus-4-5-20251101

**Status:** Implemented

#### What Was Done
- Created `.github/workflows/contract-tests.yml` with scheduled daily execution
- Added schedule trigger: cron `0 6 * * *` (daily at 6 AM UTC)
- Added `workflow_dispatch` for manual triggering via `gh workflow run contract-tests.yml`
- Implemented certificate handling: base64 decode secret to temp file with restricted permissions (chmod 600)
- Added cleanup step with `if: always()` to remove temp certificate file
- Configured environment variables (TEST_TENANT_ID, TEST_CLIENT_ID, TEST_CERT_PATH) from GitHub secrets
- Added JUnit XML output (`--junitxml=contract-test-results.xml`) for test result tracking
- Added artifact upload on failure with 7-day retention
- Added GitHub issue creation on failure using `actions/github-script@v7`
- Implemented duplicate issue prevention: checks for existing open "contract-failure" labeled issues before creating new ones
- Documented prerequisites (Azure app registration, GitHub secrets) in workflow comments

#### Key Decisions Made
- **Duplicate issue prevention**: Added logic to check for existing open issues with "contract-failure" label before creating new ones - addresses open question from recon about avoiding duplicate issues
- **Certificate in /tmp**: Used `/tmp/test-cert.pem` with chmod 600 for security; `if: always()` cleanup ensures removal even on failure
- **JUnit XML output**: Added `--junitxml` flag for structured test results that can be parsed by CI tools
- **Detailed issue body**: Includes workflow run link, timestamp, trigger type, next steps, and Graph API changelog link
- **Single "contract-failure" label**: Used consistent label for easy issue tracking and filtering

#### Patterns Discovered
- Contract tests use environment variables (TEST_TENANT_ID, TEST_CLIENT_ID, TEST_CERT_PATH) per `tests/contract/conftest.py:36-48`
- Secrets naming convention: AZURE_* prefix for Azure credentials in GitHub secrets
- Existing ci.yml and release.yml use `actions/checkout@v5` and `astral-sh/setup-uv@v7`

#### Open Items for Next Agent
- [ ] AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_CERTIFICATE secrets must be configured in GitHub repository settings
- [ ] "contract-failure" label must be created in GitHub repository before first failure creates an issue
- [ ] Test tenant must have sample data (users with phone numbers) for contract tests to pass

#### Gotchas and Warnings
- Certificate secret must be base64-encoded before storing in GitHub secrets: `base64 -w 0 < cert.pem`
- Contract tests may skip if test tenant lacks data (expected behavior per conftest.py)
- Workflow requires Issues permission to create issues - ensure workflow has write access to issues
