# BAT-002 Planning - Status: confirmed - Batch: BAT-002 - Profile: batch-lite ## Included REQ - REQ-012: service-token rotate CLI 명령 구현 ## Delivery Plan ### Out of Scope - `expires_at` 변경 옵션 (이번 rotate는 secret 교체만) - HTTP API rotate 엔드포인트 (CLI 전용) ### 구현 순서 #### 1단계: Repository 레이어 - `app/repositories/portfolio_repository.py` — `rotate_service_token(token_id, token_hash, token_prefix, runtime_id) -> AuthToken` Protocol stub 추가 - `app/repositories/postgres.py` — `rotate_service_token` UPDATE 구현 ```sql UPDATE auth_tokens SET token_hash = %s, token_prefix = %s, runtime_id = %s WHERE id = %s AND revoked_at IS NULL RETURNING id, user_id, name, token_prefix, ... ``` - 대상 행이 없거나 revoked이면 ValueError 반환 #### 2단계: Service 레이어 - `app/services/auth_service.py` — `rotate_service_token(name) -> IssuedServiceToken` 추가 - `get_service_token_by_name(name)` 조회 → 없으면 ValueError - revoked 상태이면 ValueError - `expires_at is not None`이면 ValueError ("만료일이 설정된 토큰은 rotate할 수 없습니다") - `generate_token()` + `hash_token()` + `token_prefix()` + `uuid.uuid4()` 로 새 값 생성 - `repository.rotate_service_token(...)` 호출 - `IssuedServiceToken(token=raw_token, auth_token=updated_token)` 반환 #### 3단계: CLI 명령 - `app/cli/commands/service_tokens.py` — `rotate` 명령 추가 - `--force` 없으면 확인 프롬프트 - issue 명령과 동일한 1회 원문 출력 패턴 적용 #### 4단계: 테스트 - `tests/test_cli.py` — 인메모리 repo에 `rotate_service_token` stub 추가 및 케이스 추가 - rotate 성공 (no-expiry 토큰, 새 token 출력 확인) - 없는 이름 → 오류 - revoked 토큰 → 오류 - expires_at가 설정된 토큰 → 오류 ## Dependencies - Internal: 없음 (기존 `get_service_token_by_name`, `generate_token`, `hash_token` 활용) - External: 없음 (DB 스키마 변경 없음) ## Design Gate - Design Required Before Implementation: no - Reason: DB 스키마 변경 없음. 기존 패턴(issue/revoke) 동일하게 적용. 결정 사항 없음.