Files
Orchestrator/.kiro/specs/pat-renewal/tasks.md
T
2026-06-30 20:37:40 +02:00

205 lines
12 KiB
Markdown

# Implementation Plan: PAT Renewal
## Overview
Implement an automated PAT lifecycle management system as a Python script (`scripts/pat_manager.py`) that runs via GitHub Actions. The implementation follows a modular architecture with 7 components: Registry, Expiry Checker, GitLab Rotator, Secret Updater, Alert Sender, Summary Reporter, and Main Orchestrator. All modules use Python 3.11+, httpx for HTTP, and pytest with Hypothesis for property-based testing.
## Tasks
- [x] 1. Set up project structure and core data models
- [x] 1.1 Create directory structure and dependencies
- Create `scripts/pat_manager/` package directory with `__init__.py`
- Create `scripts/pat_manager/models.py` with `PatEntry` dataclass, `PatStatus` enum, `CheckResult` dataclass, `RotationResult` dataclass, `AlertResult` dataclass, and `AlertChannel` enum
- Create `scripts/pat_manager/errors.py` with all custom exception classes (`PatManagerError`, `RegistryValidationError`, `ServiceCheckError`, `RotationError`, `SecretUpdateError`, `AlertError`)
- Add `requirements.txt` with `httpx>=0.27` and `requirements-dev.txt` with `hypothesis>=6.100`, `pytest>=8.0`, `pytest-asyncio>=0.23`, `pytest-cov>=5.0`, `respx>=0.21`
- _Requirements: 1.1, 1.4_
- [x] 1.2 Create PAT Registry JSON schema and example file
- Create `pat-registry.json` with the JSON schema structure (version "1.0", empty tokens array)
- Create `pat-registry.example.json` with example entries for all four services
- _Requirements: 1.1, 1.6_
- [x] 2. Implement PAT Registry module
- [x] 2.1 Implement registry loading and validation (`scripts/pat_manager/registry.py`)
- Implement `PatRegistry` class with `load(path)` method that reads and parses JSON
- Implement `validate_entry(entry)` that checks all required fields, service enum values, token_name length (max 128 chars), expiry_date ISO 8601 format, and renewal_method matching service
- Implement duplicate detection for `(service, token_name)` combinations
- Implement `create_empty(path)` for creating a new empty registry file
- Implement `save(path, entries)` for persisting registry changes
- Raise `RegistryValidationError` with specific failed fields on invalid entries
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6_
- [x] 2.2 Write property test: Registry serialization round-trip
- **Property 1: Registry serialization round-trip**
- **Validates: Requirements 1.1**
- Use Hypothesis to generate valid `PatEntry` lists and verify `save``load` produces equivalent entries
- [x] 2.3 Write property test: Validation accepts only valid entries
- **Property 2: Validation accepts only valid entries**
- **Validates: Requirements 1.2, 1.4**
- Use Hypothesis to generate both valid and invalid entry dicts, verify validator accepts/rejects correctly
- [x] 2.4 Write property test: Invalid entries and duplicates preserve registry state
- **Property 3: Invalid entries and duplicates preserve registry state**
- **Validates: Requirements 1.3, 1.5**
- Use Hypothesis to generate registries and invalid/duplicate entries, verify registry unchanged after rejected add
- [x] 3. Implement Expiry Checker module
- [x] 3.1 Implement expiry classification logic (`scripts/pat_manager/checker.py`)
- Implement `ExpiryChecker` class with configurable `expiry_window_days` (default 14), `timeout` (default 30s), and `max_retries` (default 2)
- Implement `classify_by_date(expiry_date, reference_date, window)` pure function for date-based classification
- Implement `check_all(entries, secrets)` async method that iterates all entries and returns `CheckResult` list
- _Requirements: 2.2, 2.3, 2.4, 2.7_
- [x] 3.2 Implement service-specific check methods
- Implement `check_gitlab(entry, token)` — calls `GET /personal_access_tokens`, reads `expires_at`, classifies
- Implement `check_jira(entry, token)` — authenticated call, HTTP 401 = expired
- Implement `check_confluence(entry, token)` — authenticated call, HTTP 401 = expired
- Implement `check_orgmylife(entry, token)` — authenticated call, HTTP 401/403 = expired
- Handle timeout (30s), retries (2 attempts with 5s backoff), and fallback to "check failed"
- Handle null `expiry_date` entries: classify based on API call result only
- Handle stale `expiry_date` warning when API succeeds but stored date has passed
- _Requirements: 2.1, 2.5, 2.6, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8_
- [x] 3.3 Write property test: Expiry classification is deterministic and correct
- **Property 4: Expiry classification is deterministic and correct**
- **Validates: Requirements 2.2, 2.3, 2.4, 6.5**
- Use Hypothesis to generate expiry dates and reference dates, verify classification matches expected rules
- [x] 3.4 Write property test: Classification completeness
- **Property 5: Classification completeness**
- **Validates: Requirements 2.7**
- Use Hypothesis to generate N-entry registries, verify exactly N results each with exactly one valid status
- [x] 3.5 Write property test: Non-auth HTTP errors yield "check failed"
- **Property 12: Non-auth HTTP errors yield "check failed"**
- **Validates: Requirements 6.6**
- Use Hypothesis to generate HTTP status codes not in {200-299, 401, 403}, verify classification is "check failed"
- [x] 4. Checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
- [x] 5. Implement GitLab Rotator module
- [x] 5.1 Implement GitLab token rotation (`scripts/pat_manager/rotator.py`)
- Implement `GitLabRotator` class with `rotate(token_id, current_token)` async method
- Call `POST /personal_access_tokens/{id}/rotate` with the current token
- Return `RotationResult` with new expiry date on success
- Implement retry logic: retry once on failure, then return error result
- Skip rotation for expired tokens (return error indicating manual intervention needed)
- _Requirements: 3.1, 3.4, 3.7_
- [x] 5.2 Write property test: Registry update after successful rotation
- **Property 6: Registry update after successful rotation**
- **Validates: Requirements 3.3**
- Use Hypothesis to generate rotation responses with new expiry dates, verify registry entry updated correctly
- [x] 6. Implement Secret Updater module
- [x] 6.1 Implement GitHub secret update (`scripts/pat_manager/secret_updater.py`)
- Implement `SecretUpdater` class with `update_secret(secret_name, secret_value)` async method
- Use GitHub API to encrypt and update repository secrets (libsodium sealed box encryption)
- Handle failure: retain previous secret value, report error without exposing token
- _Requirements: 3.2, 3.5, 7.1, 7.4, 7.5_
- [x] 7. Implement Alert Sender module
- [x] 7.1 Implement alert sending (`scripts/pat_manager/alerter.py`)
- Implement `AlertSender` class with configurable `AlertChannel` (OrgMyLife or email)
- Implement `send_alert(result)` that creates OrgMyLife task or sends email based on channel
- OrgMyLife task title format: `[PAT Renewal] {service} - {token_name}`
- Include days remaining (0 for expired) in alert message
- Implement `check_existing_task(service, token_name, expiry_date)` for idempotent alerting
- Handle alert channel unreachable: log failure, signal non-zero exit
- Implement retry logic: 1 retry with 2s backoff
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6_
- [x] 7.2 Write property test: Alert triggering correctness
- **Property 7: Alert triggering correctness**
- **Validates: Requirements 4.1, 5.4**
- Use Hypothesis to generate classified PAT sets, verify zero alerts when all healthy, one alert per expiring/expired manual token
- [x] 7.3 Write property test: Alert message completeness
- **Property 8: Alert message completeness**
- **Validates: Requirements 4.2, 4.3**
- Use Hypothesis to generate PAT entries requiring alerts, verify OrgMyLife title contains service+token, email contains all required fields
- [x] 7.4 Write property test: Days remaining calculation
- **Property 9: Days remaining calculation**
- **Validates: Requirements 4.4**
- Use Hypothesis to generate expiry/reference date pairs, verify `days_remaining == max(0, (expiry - ref).days)`
- [x] 7.5 Write property test: Alert idempotence
- **Property 10: Alert idempotence**
- **Validates: Requirements 4.5**
- Use Hypothesis to generate PAT alert scenarios, verify no duplicate task created when open task exists
- [x] 8. Implement Summary Reporter module
- [x] 8.1 Implement summary report generation (`scripts/pat_manager/reporter.py`)
- Implement `SummaryReporter` class with `report(results, rotations)` method
- Generate one line per PAT with service name, token identifier, and classification status
- Include rotation results (old/new expiry dates) for rotated tokens
- _Requirements: 5.3_
- [x] 8.2 Write property test: Summary report completeness
- **Property 11: Summary report completeness**
- **Validates: Requirements 5.3**
- Use Hypothesis to generate check result lists, verify report contains one line per result with service, token_name, and status
- [x] 9. Checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
- [x] 10. Implement Main Orchestrator and GitHub Actions workflow
- [x] 10.1 Implement main orchestrator (`scripts/pat_manager/__main__.py`)
- Implement `main()` async function that coordinates all modules
- Load registry → check all → rotate GitLab tokens → update secrets → send alerts → report summary
- Mask all token values with `::add-mask::{value}` before any operations
- Handle rotation success + secret update failure → trigger alert flow
- Return exit code: 0 (success), 1 (alert delivery failure), 2 (registry validation failure)
- Log rotation events with old and new expiration dates (Req 3.6)
- _Requirements: 3.5, 3.6, 5.4, 5.5, 7.1, 7.2_
- [x] 10.2 Create GitHub Actions workflow file
- Create `.github/workflows/pat-check.yml` with cron schedule `0 6 * * 1` (Monday 06:00 UTC)
- Add `workflow_dispatch` trigger
- Configure Python 3.11 setup, dependency installation, and script execution
- Map all required secrets as environment variables (GITLAB_PAT, JIRA_PAT, CONFLUENCE_PAT, ORGMYLIFE_API_SECRET, ORGMYLIFE_USER, ORGMYLIFE_PASS, PAT_MANAGER_TOKEN, ALERT_CHANNEL)
- Ensure all API URLs use HTTPS
- _Requirements: 5.1, 5.2, 7.3_
- [x] 10.3 Write integration tests for orchestrator flow
- Test full happy path: all healthy → no alerts, summary only
- Test GitLab rotation flow with mocked APIs (respx)
- Test rotation success + secret update failure → alert triggered
- Test alert channel unreachable → non-zero exit code
- Test service timeout after retries → "check failed" classification
- Test null expiry_date classification scenarios
- _Requirements: 2.5, 2.6, 3.2, 3.4, 3.5, 4.6, 5.4, 5.5, 6.8_
- [x] 11. Final checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation
- Property tests validate universal correctness properties from the design document using Hypothesis
- Unit/integration tests validate specific examples and edge cases using pytest + respx
- The implementation uses Python 3.11+, httpx for HTTP calls, and async/await throughout
- All token values must be masked in logs via GitHub Actions `::add-mask::` mechanism
## Task Dependency Graph
```json
{
"waves": [
{ "id": 0, "tasks": ["1.1", "1.2"] },
{ "id": 1, "tasks": ["2.1"] },
{ "id": 2, "tasks": ["2.2", "2.3", "2.4", "3.1"] },
{ "id": 3, "tasks": ["3.2", "3.3", "3.4", "3.5"] },
{ "id": 4, "tasks": ["5.1", "6.1", "7.1", "8.1"] },
{ "id": 5, "tasks": ["5.2", "7.2", "7.3", "7.4", "7.5", "8.2"] },
{ "id": 6, "tasks": ["10.1", "10.2"] },
{ "id": 7, "tasks": ["10.3"] }
]
}
```