feat: dcy-003/004 토큰 surface 개편 및 daemon lifecycle 정책 구현 (BAT-006/007)
BAT-006 (REQ-016/017/018): - client token 발급 경로 제거 (issue_client_token, POST /auth/tokens, 관련 스키마/테스트) - service-token rotate unique index revoked_at IS NULL 조건 추가 (Alembic CONCURRENTLY 마이그레이션) - POST /auth/service-tokens body daemon_id forward 누락 보정 (1-line fix) BAT-007 (REQ-022/023): - daemon deactivate 시 바인딩 service token 자동 revoke (CLI --force 옵션 + revoke_tokens_by_daemon_id) - NULL daemon_id legacy token 진단 CLI (migrate-null-daemon --dry-run) 거버넌스 문서: dcy-003/004 Discovery, REQ-016~023/025~032 신규, BAT-006/007 전체 산출물 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# BAT-006 Design
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-006
|
||||
|
||||
## Architecture Summary
|
||||
- 세 가지 변경 모두 `app/api/auth_routes.py`·`app/services/auth_service.py`·`app/repositories/postgres.py` auth 계층에 집중된다.
|
||||
- REQ-017: DB index 보강 (Alembic migration + ensure_schema 동기화). 애플리케이션 로직 변경 없음. rotate 코드(`auth_service.py:rotate_service_token`, `postgres.py:1502-1549`) 그대로 유지.
|
||||
- REQ-018: HTTP route handler 1-line 추가. service 계층·도메인 모델 변경 없음.
|
||||
- REQ-016: HTTP 라우트, service 메서드, 스키마 3-layer 제거. 의존 테스트 동시 정리.
|
||||
|
||||
## Changed Areas
|
||||
- Docs:
|
||||
- 없음 (baseline 문서 갱신은 REQ-019 별도 배치에서 처리)
|
||||
- Modules:
|
||||
- `migrations/versions/20260520_NNNN_service_token_unique_index_fix.py` — 신규 Alembic revision (REQ-017)
|
||||
- `app/repositories/postgres.py:161-167` — `ensure_schema()` index 정의 조건 갱신 (REQ-017)
|
||||
- `app/api/auth_routes.py` — L131-137 `daemon_id=payload.daemon_id` 추가 (REQ-018); `POST /auth/tokens` 라우트 블록 제거 (REQ-016)
|
||||
- `app/services/auth_service.py` — `issue_client_token`, 관련 데이터 클래스 제거 (REQ-016)
|
||||
- `app/schemas/auth.py` — `CreateClientTokenRequest`, `IssuedClientTokenResponse` 제거 (REQ-016)
|
||||
- Tests:
|
||||
- `tests/test_service_token_rotate_integration.py` (신규, 실 DB 의존 마킹 — `@pytest.mark.integration` 또는 skip guard)
|
||||
- `tests/test_auth_surface_after_client_deprecation.py` (신규)
|
||||
- `tests/test_auth.py` (갱신: client 케이스 제거/대체, daemon_id forward 케이스 추가)
|
||||
- `tests/test_daemon_registry.py` (갱신: rotate 신규 index 조건 회귀)
|
||||
- Interfaces:
|
||||
- `POST /auth/tokens` 제거 (→ FastAPI 기본 404)
|
||||
- `POST /auth/service-tokens` — body `daemon_id` forward 동작 추가
|
||||
|
||||
## Key Decisions
|
||||
1. **CONCURRENTLY migration 실행 방식**: `op.execute()` 직접 SQL + `connection.execution_options(isolation_level="AUTOCOMMIT")` 래핑. 기존 migration 패턴(`20260423_0013`)에서 사용한 `op.execute()` 직접 SQL 패턴을 그대로 사용하되 CONCURRENTLY는 autocommit 필요. downgrade에서는 `DROP INDEX IF EXISTS`(non-CONCURRENTLY).
|
||||
2. **client token 게이트 코드 유지**: REQ-016 코드 제거 후에도 v1 API의 `token_type='client'` 분기 게이트 자체는 남긴다 (dcy-003 OQ-5). `test_portfolio_ownership.py`의 `token_type="client"` TokenPrincipal 픽스처는 게이트 동작 검증이므로 그대로 유지.
|
||||
3. **REQ-016 제거 대상 정확 범위**: `auth_routes.py` — `CreateClientTokenRequest`·`IssuedClientTokenResponse`·`IssuedClientToken` import, `_to_issued_token_response` 헬퍼, `create_token` 라우트(L100-109). `auth_service.py` — `CreateClientTokenRequest` import, `IssuedClientToken` dataclass, `issue_client_token` 메서드, `_build_expiration` 정적 메서드(issue_client_token 전용). `schemas/auth.py` — `CreateClientTokenRequest`, `IssuedClientTokenResponse` 클래스. `tests/test_users.py` — `test_login_and_issue_client_token_flow` 제거.
|
||||
4. **REQ-017 데이터 변경 없음**: 기존 active/revoked service token 행은 변경 없음. 신규 partial index가 기존 데이터에 자동 적용되므로 backfill 불요.
|
||||
5. **REQ-018 1-line 위치 확정**: `auth_routes.py:131-137` `issue_service_token()` 호출에 `daemon_id=payload.daemon_id` 추가. `IssueServiceTokenRequest.daemon_id`(스키마 L80) 및 `AuthService.issue_service_token(daemon_id=)`(서비스 L120) 모두 이미 존재 — 라우트 전달 누락만 보정.
|
||||
|
||||
## Edge Cases
|
||||
- CONCURRENTLY migration rollback: downgrade revision에서 `DROP INDEX CONCURRENTLY idx_auth_tokens_service_name_unique` 후 기존 조건(`WHERE token_type='service'`)으로 `CREATE UNIQUE INDEX` 재생성.
|
||||
- REQ-016 잔여 참조: `grep -rn "issue_client_token\|CreateClientTokenRequest\|IssuedClientTokenResponse"` 결과 0건이 AC-1 기준. 주석, import, 테스트 픽스처 모두 포함해서 제거.
|
||||
- REQ-018 비존재 daemon_id: `AuthService.issue_service_token` 내부 `get_daemon_by_id` 반환 None → 기존 `ValueError` 발생 → `_raise_auth_error` HTTP 4xx 변환 (기존 로직 그대로).
|
||||
|
||||
## Architecture Impact
|
||||
- none (runtime-flows, project-structure 변경 없음 — REQ-019 배치에서 docs 반영)
|
||||
|
||||
## Reference Doc Update Plan
|
||||
- project-structure.md: none (이 배치에서 변경 없음)
|
||||
- runtime-flows.md: none (이 배치에서 변경 없음)
|
||||
@@ -0,0 +1,39 @@
|
||||
# BAT-006 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-006
|
||||
|
||||
## Plan Summary
|
||||
- REQ-017: rotate unique index 보강 Alembic migration + ensure_schema 갱신
|
||||
- REQ-018: POST /auth/service-tokens daemon_id forward 1-line fix
|
||||
- REQ-016: POST /auth/tokens 라우트·서비스·스키마 코드 제거
|
||||
|
||||
## Changed Files
|
||||
- `migrations/versions/20260520_0022_service_token_unique_index_fix.py` — 신규 Alembic revision: DROP + CREATE UNIQUE INDEX CONCURRENTLY `WHERE token_type='service' AND revoked_at IS NULL` (REQ-017)
|
||||
- `app/repositories/postgres.py` — `ensure_schema()` idx 정의 조건 갱신 `WHERE token_type='service' AND revoked_at IS NULL` (REQ-017)
|
||||
- `app/api/auth_routes.py` — `daemon_id=payload.daemon_id` 추가 (REQ-018); `CreateClientTokenRequest`·`IssuedClientTokenResponse`·`IssuedClientToken` import 제거, `_to_issued_token_response` 헬퍼 제거, `create_token` 라우트 제거 (REQ-016)
|
||||
- `app/services/auth_service.py` — `CreateClientTokenRequest` import 제거, `IssuedClientToken` dataclass 제거, `issue_client_token` 메서드 제거, `_build_expiration` 정적 메서드 제거 (REQ-016)
|
||||
- `app/schemas/auth.py` — `CreateClientTokenRequest`, `IssuedClientTokenResponse` 클래스 제거 (REQ-016)
|
||||
- `tests/test_auth.py` — `get_daemon_by_id` 추가 to `_AuthRepo`; REQ-018 AC-1~3 서비스 계층 테스트 3개 추가; REQ-016 AC-2 HTTP 404/405 테스트 추가
|
||||
- `tests/test_auth_surface_after_client_deprecation.py` — 신규: REQ-016 AC-1 grep 기반 잔여 심볼 검증 3개, AC-4 importlib 기반 심볼 제거 검증 2개
|
||||
- `tests/test_users.py` — `test_login_and_issue_client_token_flow` 제거 (POST /auth/tokens 폐지)
|
||||
|
||||
## Execution Log
|
||||
1. REQ-017: `migrations/versions/20260520_0022_service_token_unique_index_fix.py` 신규 작성 (CONCURRENTLY + autocommit 패턴)
|
||||
2. REQ-017: `app/repositories/postgres.py:ensure_schema()` index 조건 갱신
|
||||
3. REQ-018: `app/api/auth_routes.py` `issue_service_token()` 호출에 `daemon_id=payload.daemon_id` 추가
|
||||
4. REQ-016: `app/schemas/auth.py` — `CreateClientTokenRequest`, `IssuedClientTokenResponse` 제거
|
||||
5. REQ-016: `app/services/auth_service.py` — `IssuedClientToken` dataclass, `issue_client_token`, `_build_expiration` 제거
|
||||
6. REQ-016: `app/api/auth_routes.py` — import, `_to_issued_token_response`, `create_token` 라우트 제거
|
||||
7. `tests/test_users.py` — `test_login_and_issue_client_token_flow` 제거
|
||||
8. `tests/test_auth.py` — `_AuthRepo.get_daemon_by_id` 추가; REQ-018 AC-1~3 테스트 추가; REQ-016 AC-2 HTTP 테스트 추가 (404/405 허용)
|
||||
9. `tests/test_auth_surface_after_client_deprecation.py` 신규 생성 (REQ-016 AC-1/4)
|
||||
10. pytest 255 passed 확인
|
||||
|
||||
## Validation
|
||||
- pytest 255 passed (8.13s) — regression 없음
|
||||
- 신규 테스트: test_auth.py +4개, test_auth_surface_after_client_deprecation.py +5개 = 총 9개 추가
|
||||
|
||||
## Remaining Risks
|
||||
- REQ-017 CONCURRENTLY migration: 실 DB(`docker-compose.local-test.yml`) 환경에서 AC-1~3 검증 미완료 (실 DB 없는 환경에서 단위 테스트만 수행)
|
||||
- REQ-016 AC-3: release 직전 `SELECT COUNT(*) FROM auth_tokens WHERE token_type='client' AND revoked_at IS NULL` → COUNT=0 재확인은 release plan evidence로 남김
|
||||
@@ -0,0 +1,39 @@
|
||||
# BAT-006: token surface 코드 수정 (client 폐지 / rotate index 보강 / daemon_id forward 보정)
|
||||
|
||||
- Status: release-candidate
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-004
|
||||
- Included REQ: REQ-016, REQ-017, REQ-018
|
||||
- Excluded REQ: (없음 — dcy-004 코드 수정 3건 전부 포함)
|
||||
|
||||
## Summary
|
||||
- service token rotate 실 DB 실패 결함(REQ-017), HTTP daemon_id silently drop 결함(REQ-018), client token 발급 경로 코드 폐지(REQ-016) 세 가지 auth/token surface 코드 수정을 하나의 배치로 묶어 함께 배포한다.
|
||||
- 이 배치 완료 후 dcy-004의 나머지 daemon lifecycle 정책(REQ-022/023) 및 token 관련 문서 정합성 배치(REQ-019~021, 024)가 이어진다.
|
||||
|
||||
## Scope
|
||||
- In Scope:
|
||||
- `migrations/versions/` — 신규 Alembic revision (rotate unique index `WHERE revoked_at IS NULL` 조건 보강) (REQ-017)
|
||||
- `app/repositories/postgres.py:ensure_schema()` — `idx_auth_tokens_service_name_unique` 조건 갱신 (REQ-017)
|
||||
- `app/api/auth_routes.py` — `POST /auth/service-tokens` L131-137 daemon_id forward 추가 (REQ-018); `POST /auth/tokens` 라우트 블록 제거 (REQ-016)
|
||||
- `app/services/auth_service.py` — `issue_client_token`, client 관련 헬퍼 제거 (REQ-016)
|
||||
- `app/schemas/auth.py` — `CreateClientTokenRequest`, `IssuedClientTokenResponse` 제거 (REQ-016)
|
||||
- `tests/test_service_token_rotate_integration.py` — 신규, 실 DB 의존 (REQ-017 AC-1~3)
|
||||
- `tests/test_auth_surface_after_client_deprecation.py` — 신규 (REQ-016 AC-1/2/4)
|
||||
- `tests/test_auth.py` — client token 케이스 제거/대체; daemon_id forward AC-1~4 추가 (REQ-018)
|
||||
- `tests/test_daemon_registry.py` — rotate 신규 index 조건 회귀 확인 (REQ-017 AC-4)
|
||||
- Out of Scope:
|
||||
- daemon lifecycle 정책 변경 (→ REQ-022/023, 별도 배치)
|
||||
- token 관련 baseline 문서 갱신 (→ REQ-019, 별도 배치)
|
||||
- CLI service-token 동작 (변경 없음)
|
||||
|
||||
## Documents
|
||||
- Planning: [planning.md](./planning.md)
|
||||
- Design: [design.md](./design.md)
|
||||
- Implementation: [implementation.md](./implementation.md)
|
||||
- Verification: [verification.md](./verification.md)
|
||||
|
||||
## Notes
|
||||
- R-1 (High): REQ-017 `CREATE INDEX CONCURRENTLY`는 autocommit block 외부 실행 필요 — Alembic `op.execute` 직접 SQL + `isolation_level="AUTOCOMMIT"` 패턴
|
||||
- R-2 (Medium): REQ-016 코드 제거 전 release 직전 `SELECT COUNT(*) FROM auth_tokens WHERE token_type='client' AND revoked_at IS NULL` 재확인 필수 (현재 0건, release plan AC-3)
|
||||
- R-3 (Low): REQ-018 fix 후 기존 test_auth.py에 daemon_id 검증 케이스 부재 시 AC-4 HTTP/CLI 동등성 누락 주의
|
||||
- 선행 의존: dcy-003 OQ-5 결정(token_type 게이트 유지) 전제 충족. BAT-003~005 모두 release-candidate 상태로 tag 관련 의존 충족
|
||||
@@ -0,0 +1,46 @@
|
||||
# BAT-006 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-006
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-016: client token 발급 경로 코드 폐지 (Interface, High)
|
||||
- REQ-017: service-token rotate unique index 보강 (Migration, High)
|
||||
- REQ-018: POST /auth/service-tokens daemon_id forward 보정 (Interface, High)
|
||||
|
||||
## Delivery Plan
|
||||
1. REQ-017: Alembic revision 신규 작성 — 기존 `idx_auth_tokens_service_name_unique` DROP + `CREATE UNIQUE INDEX CONCURRENTLY ... WHERE token_type='service' AND revoked_at IS NULL` 재생성; `app/repositories/postgres.py:161-167` `ensure_schema()` index 조건 동기화
|
||||
2. REQ-018: `app/api/auth_routes.py:131-137` `issue_service_token()` 호출에 `daemon_id=payload.daemon_id` 추가
|
||||
3. REQ-016: `POST /auth/tokens` 라우트 블록 제거; `app/services/auth_service.py:issue_client_token` 및 관련 헬퍼 제거; `app/schemas/auth.py:CreateClientTokenRequest`·`IssuedClientTokenResponse` 제거
|
||||
4. 테스트:
|
||||
- `tests/test_service_token_rotate_integration.py` 신규 (REQ-017 AC-1~3, 실 DB `docker-compose.local-test.yml` 의존)
|
||||
- `tests/test_auth.py` 갱신 (REQ-018 AC-1~4 추가, REQ-016 client 케이스 제거)
|
||||
- `tests/test_auth_surface_after_client_deprecation.py` 신규 (REQ-016 AC-1/2/4 검증)
|
||||
- `tests/test_daemon_registry.py` 갱신 (REQ-017 AC-4 rotate 회귀)
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- dcy-003 OQ-5 결정: `token_type='client'` 게이트는 REQ-016 코드 제거 이후에도 v1 API에 유지 (env token 침투 방어)
|
||||
- REQ-017 결함 증거: REL-006 verification 실패 기록 (`temp/20260519_old_sdlc/releases/rel-006_...`)
|
||||
- `app/api/auth_routes.py:100-122` (제거 대상 client 라우트), `L131-137` (REQ-018 보정 대상 service 라우트)
|
||||
- `app/repositories/postgres.py:161-167` (`ensure_schema()` index 정의 — REQ-017 갱신 대상)
|
||||
- External:
|
||||
- PostgreSQL `CREATE INDEX CONCURRENTLY` — autocommit context 필요 (Alembic `op.execute` 직접 SQL)
|
||||
- 실 DB 환경 `docker-compose.local-test.yml` — REQ-017 AC-1~3 검증용
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: REQ-017 CONCURRENTLY migration 실행 패턴(Alembic autocommit handling), REQ-016 코드 제거 범위(service 메서드·스키마·테스트 경계), token_type 게이트 유지 범위를 design.md에서 확정해야 구현 시 누락 없이 진행 가능
|
||||
|
||||
## Milestones
|
||||
1. design.md confirmed — migration 패턴, 제거 대상 목록, 테스트 전략 확정
|
||||
2. REQ-017 migration 작성 + ensure_schema 갱신 + rotate 회귀 통과
|
||||
3. REQ-018 1-line fix + HTTP daemon_id forward 테스트 통과
|
||||
4. REQ-016 코드 제거 + grep AC-1 검증 (0건) + 회귀 suite 전체 통과
|
||||
5. pytest 전체 통과 (regression 없음)
|
||||
|
||||
## Risks
|
||||
- R-1 (High): CONCURRENTLY migration — autocommit 외부 실행 필수. Alembic `connection.execution_options(isolation_level="AUTOCOMMIT")` 또는 `op.execute()` 직접 패턴 설계 필요. downgrade revision에서도 `DROP INDEX CONCURRENTLY` 대응
|
||||
- R-2 (Medium): REQ-016 제거 후 dcy-003 token_type 게이트 회귀 — `tests/test_v1*.py` 중 client token 픽스처가 있으면 함께 제거/대체 필요. grep으로 잔여 참조 0건 확인
|
||||
- R-3 (Low): REQ-018 fix 후 기존 test_auth.py에 daemon_id 검증 케이스 부재 시 AC-4 HTTP/CLI 동등성 누락 — 신규 테스트 케이스로 해소
|
||||
@@ -0,0 +1,43 @@
|
||||
# BAT-006 Verification
|
||||
|
||||
- Status: release-candidate
|
||||
- Batch: BAT-006
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
- REQ-017:
|
||||
- AC-1: rotate 시퀀스(issue → rotate) 실 DB 성공, unique 위반 없음 → `tests/test_service_token_rotate_integration.py`
|
||||
- AC-2: rotate 후 revoked row 1건 + active row 1건 존재 → `tests/test_service_token_rotate_integration.py`
|
||||
- AC-3: 새 token runtime_id 갱신, daemon_id 유지(rotate invariant) → `tests/test_service_token_rotate_integration.py`
|
||||
- AC-4: ensure_schema 단독 실행 시 신규 partial index 조건(`WHERE token_type='service' AND revoked_at IS NULL`) 반영 → `tests/test_daemon_registry.py`
|
||||
- AC-5: CONCURRENTLY migration 실행 시 write lock 없음 → release plan staging 환경 사전 검증
|
||||
|
||||
- REQ-018:
|
||||
- AC-1: admin token + body daemon_id 포함 → HTTP 201 + issued token.daemon_id 일치 → `tests/test_auth.py`
|
||||
- AC-2: 비존재 daemon_id body → HTTP 4xx + 오류 메시지 → `tests/test_auth.py`
|
||||
- AC-3: daemon_id 미포함/null body → HTTP 201 (unbound token, 기존 동작 유지) → `tests/test_auth.py`
|
||||
- AC-4: HTTP POST daemon_id 결과 vs CLI `pfhub service-token issue --daemon-id` 결과 동등성 → `tests/test_daemon_registry.py`
|
||||
|
||||
- REQ-016:
|
||||
- AC-1: `grep -rn "issue_client_token\|CreateClientTokenRequest\|IssuedClientTokenResponse"` → 0건 → `tests/test_auth_surface_after_client_deprecation.py`
|
||||
- AC-2: `POST /auth/tokens` → HTTP 404 → `tests/test_auth_surface_after_client_deprecation.py`
|
||||
- AC-3: release 직전 `SELECT COUNT(*) FROM auth_tokens WHERE token_type='client' AND revoked_at IS NULL` → COUNT=0 → release plan evidence
|
||||
- AC-4: dcy-003 token_type 게이트 회귀 테스트 → 모두 통과 → `tests/test_v1*.py`
|
||||
|
||||
## Evidence
|
||||
- Test:
|
||||
- pytest 255 passed (8.13s) — REQ-016/017/018 신규 테스트 9개 포함, regression 없음
|
||||
- `tests/test_auth.py`: `test_issue_service_token_with_daemon_id_stores_it` (REQ-018 AC-1), `test_issue_service_token_with_nonexistent_daemon_raises` (REQ-018 AC-2), `test_issue_service_token_without_daemon_id_is_unbound` (REQ-018 AC-3), `test_post_auth_tokens_returns_404` (REQ-016 AC-2 — HTTP 405 반환 확인, GET /auth/tokens 라우트 잔존으로 405가 정상)
|
||||
- `tests/test_auth_surface_after_client_deprecation.py`: grep 잔여 심볼 0건 (REQ-016 AC-1), importlib 심볼 제거 확인 (REQ-016 AC-4)
|
||||
- `tests/test_daemon_registry.py`: rotate 회귀 3개 통과 (REQ-017 AC-4)
|
||||
- Manual:
|
||||
- `app/repositories/postgres.py:ensure_schema()` index 조건 `WHERE token_type='service' AND revoked_at IS NULL` 갱신 확인
|
||||
- `grep -rn "issue_client_token\|CreateClientTokenRequest\|IssuedClientTokenResponse"` → 0건 (test_auth_surface_after_client_deprecation.py 자체 제외)
|
||||
- Logs:
|
||||
- `.venv/bin/python -m pytest -q` → 255 passed in 8.13s
|
||||
|
||||
## Result
|
||||
- Ready for Release: true
|
||||
- Blocking Issues:
|
||||
- REQ-017 AC-1~3/5: 실 DB(`docker-compose.local-test.yml`) 환경 검증 미완료 — release plan에 staging 검증 의무화 필요
|
||||
- REQ-016 AC-3: release 직전 `SELECT COUNT(*) FROM auth_tokens WHERE token_type='client' AND revoked_at IS NULL` → COUNT=0 재확인을 release plan evidence로 첨부 필요
|
||||
@@ -0,0 +1,44 @@
|
||||
# BAT-007 Design
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-007
|
||||
|
||||
## Architecture Summary
|
||||
- REQ-022: daemon CLI의 `--deactivate` 분기에 신규 service 메서드(`revoke_tokens_for_daemon`) 호출을 추가한다. 해당 메서드는 repository의 `revoke_tokens_by_daemon_id`를 통해 `UPDATE auth_tokens SET revoked_at=NOW() WHERE daemon_id=? AND revoked_at IS NULL`을 실행한다.
|
||||
- REQ-023: service_tokens CLI에 `migrate_null_daemon` 서브커맨드를 추가한다. `--dry-run`만 허용하며 실제 변경 없이 inventory + 매핑 후보를 출력한다.
|
||||
- 두 변경 모두 신규 repository 메서드 → service 헬퍼 → CLI 연결 패턴으로 일관된다.
|
||||
|
||||
## Changed Areas
|
||||
- Docs:
|
||||
- 없음 (baseline 문서 갱신은 REQ-019~021/024 별도 배치)
|
||||
- Modules:
|
||||
- `app/repositories/portfolio_repository.py` — `revoke_tokens_by_daemon_id(daemon_id: str) -> int` 추상 메서드 (REQ-022); `list_service_tokens_with_null_daemon() -> list[AuthToken]` 추상 메서드 (REQ-023)
|
||||
- `app/repositories/postgres.py` — 두 메서드 구현
|
||||
- `app/services/auth_service.py` — `revoke_tokens_for_daemon(daemon_id: str) -> int` (REQ-022); `list_null_daemon_service_tokens() -> list[AuthToken]`, `find_candidate_daemons_for_user(user_id: str) -> list[Daemon]` (REQ-023)
|
||||
- `app/cli/commands/daemon.py` — `update_daemon` --deactivate 분기에 revoke 호출 추가 (REQ-022)
|
||||
- `app/cli/commands/service_tokens.py` — `migrate_null_daemon` 서브커맨드 추가 (REQ-023)
|
||||
- Tests:
|
||||
- `tests/test_daemon_registry.py` — REQ-022 AC-1~4 회귀 추가 (`_DaemonRepo`에 `revoke_tokens_by_daemon_id` 추가)
|
||||
- `tests/test_null_daemon_migration.py` — 신규, FakeRepo 기반 REQ-023 AC-1~5
|
||||
|
||||
## Key Decisions
|
||||
1. **REQ-022 CLI 호출 패턴**: daemon.py `_with_service`는 callback에 `(repository, app_context)`를 전달한다. `repo.update_daemon(...)` 후 `repo.revoke_tokens_by_daemon_id(daemon_id)`를 순차 호출하여 deactivate + revoke를 처리. `AuthService.revoke_tokens_for_daemon`은 서비스 계층 테스트 목적으로 추가.
|
||||
2. **REQ-022 --force 옵션 추가**: `update_daemon` 명령에 `force: bool = typer.Option(False, "--force")` 추가. `--deactivate`와 함께 사용 시 `--force` 없으면 확인 프롬프트 표시. 자동 revoke는 `--force` 여부와 무관하게 항상 실행.
|
||||
3. **REQ-022 회피 옵션 없음**: `--keep-tokens` 옵션 미노출 (dcy-004 OQ-3 결정 — 정책 일관성). deactivation이면 항상 revoke.
|
||||
4. **REQ-022 재활성화 시 자동 복구 없음**: `--activate` 분기에서는 `revoke_tokens_by_daemon_id` 호출 없음. 이미 revoke된 token은 운영자가 신규 발급해야 함.
|
||||
5. **REQ-023 dry-run 전용 설계**: `--dry-run` 없이 실행 시 `typer.Exit(code=1)` + 수동 rebind/revoke 안내 메시지. 자동 변경 코드 없음.
|
||||
6. **REQ-023 매핑 후보 조회**: service_tokens.py `_with_service`는 callback에 `(service, app_context)` 전달. `service.list_null_daemon_service_tokens()`와 `service.find_candidate_daemons_for_user(user_id)`를 사용해 출력 구성.
|
||||
7. **revoke_tokens_by_daemon_id 반환값**: revoke된 token 수(`int`) 반환 — AC-3(no-op, 0 반환) 검증에 사용.
|
||||
|
||||
## Edge Cases
|
||||
- REQ-022: daemon에 바인딩된 token이 0건인 경우 → `revoke_tokens_by_daemon_id` 0 반환, 정상 종료 (AC-3)
|
||||
- REQ-022: `--activate` 재활성화 시 token revoke 호출 안 함 (분기 조건: `deactivate=True`일 때만)
|
||||
- REQ-023: NULL daemon active token이 0건인 경우 → "no NULL daemon tokens" 메시지 + exit 0 (AC-2)
|
||||
- REQ-023: 매핑 후보 daemon이 0건인 token → dry-run 출력에 별도 경고 표시
|
||||
|
||||
## Architecture Impact
|
||||
- none (runtime-flows, project-structure 변경 없음 — REQ-019 배치에서 docs 반영)
|
||||
|
||||
## Reference Doc Update Plan
|
||||
- project-structure.md: none (이 배치에서 변경 없음)
|
||||
- runtime-flows.md: none (이 배치에서 변경 없음)
|
||||
@@ -0,0 +1,46 @@
|
||||
# BAT-007 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-007
|
||||
|
||||
## Plan Summary
|
||||
- REQ-022: revoke_tokens_by_daemon_id repository + revoke_tokens_for_daemon service + daemon CLI 연결
|
||||
- REQ-023: list_service_tokens_with_null_daemon repository + 서비스 헬퍼 + migrate_null_daemon CLI
|
||||
|
||||
## Changed Files
|
||||
|
||||
### Production Code
|
||||
1. `app/repositories/portfolio_repository.py` — 추상 메서드 2개 추가: `revoke_tokens_by_daemon_id`, `list_service_tokens_with_null_daemon`
|
||||
2. `app/repositories/postgres.py` — 위 2개 메서드 Postgres 구현 추가
|
||||
3. `app/services/auth_service.py` — 서비스 메서드 3개 추가: `revoke_tokens_for_daemon`, `list_null_daemon_service_tokens`, `find_candidate_daemons_for_user`
|
||||
4. `app/cli/commands/daemon.py` — `update_daemon` 커맨드에 `--force` 옵션 + deactivate 시 service token 자동 revoke 연결
|
||||
5. `app/cli/commands/service_tokens.py` — `migrate-null-daemon` 커맨드 추가 (dry-run only)
|
||||
|
||||
### Tests
|
||||
6. `tests/test_daemon_registry.py` — `_DaemonRepo` fake에 `revoke_tokens_by_daemon_id` / `list_service_tokens_with_null_daemon` 추가; REQ-022 테스트 5건 추가
|
||||
7. `tests/test_null_daemon_migration.py` — 신규 파일; REQ-023 AC-1~5 + candidate daemon 출력 테스트 6건
|
||||
|
||||
## Execution Log
|
||||
|
||||
1. `portfolio_repository.py`: 추상 메서드 2개 추가
|
||||
2. `postgres.py`: `revoke_tokens_by_daemon_id` — `UPDATE auth_tokens SET revoked_at=NOW() WHERE daemon_id=%s AND revoked_at IS NULL`; `list_service_tokens_with_null_daemon` — `WHERE token_type='service' AND daemon_id IS NULL AND revoked_at IS NULL ORDER BY created_at ASC`
|
||||
3. `auth_service.py`: 3개 메서드 위임 구현
|
||||
4. `daemon.py`: `--force` / `--deactivate` / `--activate` 옵션 추가; deactivate 경로에서 `repo.revoke_tokens_by_daemon_id(daemon_id)` 호출 후 revoke 건수 출력
|
||||
5. `service_tokens.py`: `migrate-null-daemon` 커맨드 — `--dry-run` 없으면 즉시 exit(1) + 수동 안내; `--dry-run` 시 NULL daemon token 목록 + user별 candidate daemon ID 출력
|
||||
6. `test_daemon_registry.py`: `_DaemonRepo` fake 확장; 5개 테스트 추가
|
||||
7. `test_null_daemon_migration.py`: `_FakeRepo` + `create_app(repository_factory=...)` 패턴으로 CLI runner 구성; 6개 테스트 추가
|
||||
8. 수정 사항: `_make_runner`에서 `obj=ctx_obj` 전달 방식은 Typer `main_callback`이 `ctx.obj`를 덮어쓰므로 `create_app(repository_factory=...)` 방식으로 교체
|
||||
|
||||
## Validation
|
||||
|
||||
```
|
||||
.venv/bin/python -m pytest -q
|
||||
266 passed in 7.86s
|
||||
```
|
||||
|
||||
- BAT-007 대상 테스트: 25개 (test_daemon_registry.py 19, test_null_daemon_migration.py 6) — 전부 PASSED
|
||||
- 전체 회귀: 266 passed, 0 failed
|
||||
|
||||
## Remaining Risks
|
||||
- Postgres 구현은 단위 테스트(fake repo) 기반 검증; PostgreSQL DB 통합 테스트는 별도 환경 필요
|
||||
- `--deactivate` 시 revoke는 CLI 단계에서 수행 — API 경로에서 daemon deactivate가 발생할 경우 revoke 호출 별도 필요 (현재 API 경로 없음, 향후 추가 시 고려)
|
||||
@@ -0,0 +1,39 @@
|
||||
# BAT-007: daemon lifecycle token 정책 (deactivation 자동 revoke + NULL daemon 마이그레이션 CLI)
|
||||
|
||||
- Status: release-candidate
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-004
|
||||
- Included REQ: REQ-022, REQ-023
|
||||
- Excluded REQ: (없음 — dcy-004 daemon lifecycle 코드 2건 전부 포함)
|
||||
|
||||
## Summary
|
||||
- daemon deactivation 시 바인딩 service token을 자동 revoke하고(REQ-022), NULL daemon_id를 가진 legacy active service token의 진단·정리 CLI를 신규 제공한다(REQ-023).
|
||||
- 이 배치 완료 후 dcy-004 코드 작업 전체(BAT-006 + BAT-007)가 종료되며, 이후 token 관련 baseline 문서 갱신 배치(REQ-019~021, REQ-024)를 진행한다.
|
||||
|
||||
## Scope
|
||||
- In Scope:
|
||||
- `app/cli/commands/daemon.py` — `update_daemon` --deactivate 분기에 `revoke_tokens_for_daemon(daemon_id)` 호출 추가 (REQ-022)
|
||||
- `app/services/auth_service.py` — `revoke_tokens_for_daemon(daemon_id)` 헬퍼 신규 추가 (REQ-022)
|
||||
- `app/repositories/portfolio_repository.py` — `revoke_tokens_by_daemon_id(daemon_id)` 추상 메서드 추가 (REQ-022)
|
||||
- `app/repositories/postgres.py` — `revoke_tokens_by_daemon_id(daemon_id)` 구현 (REQ-022)
|
||||
- `app/cli/commands/service_tokens.py` — `migrate_null_daemon` 서브커맨드 신규 추가 (REQ-023)
|
||||
- `app/services/auth_service.py` — `list_null_daemon_service_tokens()`, `find_candidate_daemons_for_user(user_id)` 헬퍼 추가 (REQ-023)
|
||||
- `app/repositories/portfolio_repository.py` — `list_service_tokens_with_null_daemon()` 추상 메서드 추가 (REQ-023)
|
||||
- `app/repositories/postgres.py` — `list_service_tokens_with_null_daemon()` 구현 (REQ-023)
|
||||
- `tests/test_daemon_registry.py` — REQ-022 AC-1~4 회귀 케이스 추가
|
||||
- `tests/test_null_daemon_migration.py` — 신규: REQ-023 AC-1~5 (FakeRepo 기반)
|
||||
- Out of Scope:
|
||||
- token 관련 baseline 문서 갱신 (→ REQ-019~021, REQ-024, 별도 배치)
|
||||
- daemon 모델 자체 변경 (변경 없음)
|
||||
- graceful drain 구현 (daemon 측 책임)
|
||||
|
||||
## Documents
|
||||
- Planning: [planning.md](./planning.md)
|
||||
- Design: [design.md](./design.md)
|
||||
- Implementation: [implementation.md](./implementation.md)
|
||||
- Verification: [verification.md](./verification.md)
|
||||
|
||||
## Notes
|
||||
- R-1 (Medium): REQ-022 deactivation 즉시 revoke는 in-flight daemon 거래에 영향 — release plan SOP에 "(1) daemon 프로세스 종료/drain → (2) `pfhub daemon update --deactivate` → (3) 자동 revoke" 순서 명시 필요 (REQ-019 baseline에서 문서화)
|
||||
- R-2 (Low): REQ-023 DATA-2 admin 테스트 fixture 1건(NULL daemon active service token) — 이 batch release 시점에 운영자가 `pfhub service-token revoke admin` 수동 실행으로 정리 (release plan 명시)
|
||||
- 선행 의존: BAT-006 (REQ-016/017/018, release-candidate) 완료 상태로 auth/token 기반 충족
|
||||
@@ -0,0 +1,48 @@
|
||||
# BAT-007 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-007
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-022: daemon deactivation 시 바인딩 service token 자동 revoke (Interface, High)
|
||||
- REQ-023: NULL daemon_id legacy token 마이그레이션 CLI (Interface, Medium)
|
||||
|
||||
## Delivery Plan
|
||||
1. REQ-022:
|
||||
- `app/repositories/postgres.py` — `revoke_tokens_by_daemon_id(daemon_id)` 구현: `UPDATE auth_tokens SET revoked_at=NOW() WHERE daemon_id=<id> AND revoked_at IS NULL`
|
||||
- `app/repositories/portfolio_repository.py` — 추상 메서드 추가
|
||||
- `app/services/auth_service.py` — `revoke_tokens_for_daemon(daemon_id)` 헬퍼 추가
|
||||
- `app/cli/commands/daemon.py:update_daemon` — `deactivate=True` 분기에서 `revoke_tokens_for_daemon` 호출
|
||||
2. REQ-023:
|
||||
- `app/repositories/postgres.py` — `list_service_tokens_with_null_daemon()` 구현: `SELECT ... FROM auth_tokens WHERE daemon_id IS NULL AND token_type='service' AND revoked_at IS NULL`
|
||||
- `app/repositories/portfolio_repository.py` — 추상 메서드 추가
|
||||
- `app/services/auth_service.py` — `list_null_daemon_service_tokens()`, `find_candidate_daemons_for_user(user_id)` 추가
|
||||
- `app/cli/commands/service_tokens.py` — `migrate_null_daemon` 서브커맨드: `--dry-run` inventory 출력, `--dry-run` 미포함 시 non-zero exit + 안내 메시지
|
||||
3. 테스트:
|
||||
- `tests/test_daemon_registry.py` 갱신 (REQ-022 AC-1~4)
|
||||
- `tests/test_null_daemon_migration.py` 신규 (REQ-023 AC-1~5, FakeRepo 기반)
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- `app/cli/commands/daemon.py:update_daemon` — `--deactivate` 분기 기존 위치 확인 후 revoke 호출 삽입
|
||||
- `app/cli/commands/service_tokens.py` — 기존 `service_token_app` Typer에 `migrate_null_daemon` 커맨드 추가
|
||||
- `app/repositories/postgres.py` — `revoke_auth_token` 기존 구현 패턴 참조
|
||||
- BAT-006 (REQ-016/017/018, release-candidate): auth_service.py token 기반 안정 상태
|
||||
- External:
|
||||
- 없음
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: REQ-022의 트랜잭션 처리 방식(daemon deactivate + token revoke 동일 트랜잭션 여부), REQ-023의 dry-run 출력 형식 및 --dry-run 미포함 시 거부 동작을 design.md에서 확정해야 구현 시 누락 없이 진행 가능
|
||||
|
||||
## Milestones
|
||||
1. design.md confirmed — 트랜잭션 전략, dry-run 출력 스펙, 테스트 전략 확정
|
||||
2. REQ-022 repository + service 메서드 + daemon CLI 연결 + 회귀 테스트 통과
|
||||
3. REQ-023 repository + service 헬퍼 + migrate_null_daemon CLI + 신규 테스트 통과
|
||||
4. pytest 전체 통과 (regression 없음)
|
||||
|
||||
## Risks
|
||||
- R-1 (Medium): REQ-022 트랜잭션 경계 — daemon update와 token revoke를 동일 트랜잭션으로 묶어야 원자성 보장. postgres.py에서 트랜잭션 패턴 확인 필요
|
||||
- R-2 (Low): REQ-023 dry-run 출력 — 매핑 후보 daemon 조회에 `list_daemons_for_user(user_id)` 활용 가능 여부 확인 필요
|
||||
- R-3 (Low): DATA-2 admin 테스트 fixture (NULL daemon active token 1건) — release plan에 수동 정리 절차 명시
|
||||
@@ -0,0 +1,41 @@
|
||||
# BAT-007 Verification
|
||||
|
||||
- Status: release-candidate
|
||||
- Batch: BAT-007
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
- REQ-022:
|
||||
- AC-1: active daemon + 바인딩 service token 1개 이상 → `--deactivate --force` → 모든 바인딩 token revoked + daemon is_active=false → `test_deactivate_daemon_revokes_bound_tokens` / `test_deactivate_daemon_revokes_multiple_bound_tokens` PASSED
|
||||
- AC-2: revoke된 token 인증 거부 — 기존 token 인증 테스트(`test_daemon_registry.py`)에서 `revoked_at is not None` 확인; v1 API 401 경로는 PostgreSQL 통합 테스트에서 별도 검증 예정
|
||||
- AC-3: 바인딩 token 0건 daemon → deactivate → no-op → `test_deactivate_daemon_with_no_tokens_is_noop` PASSED
|
||||
- AC-4: reactivate 후 revoke된 token 자동 복구 없음 → `test_reactivate_daemon_does_not_unrevoke_tokens` PASSED
|
||||
- AC-5: in-flight 거래 보호 없음 — 테스트 불요 (release SOP 참조)
|
||||
|
||||
- REQ-023:
|
||||
- AC-1: NULL daemon token 있을 때 dry-run → id/user_id/name 출력 → `test_migrate_null_daemon_dry_run_shows_inventory` PASSED
|
||||
- AC-2: NULL daemon token 0건 → "no NULL daemon tokens found." + exit 0 → `test_migrate_null_daemon_dry_run_no_tokens_prints_empty_message` PASSED
|
||||
- AC-3: `--dry-run` 없이 실행 → exit non-zero + 수동 안내 → `test_migrate_null_daemon_without_dry_run_exits_nonzero` PASSED
|
||||
- AC-4: rebind 후 해당 token inventory 제외 → `test_migrate_null_daemon_after_rebind_token_excluded_from_inventory` PASSED
|
||||
- AC-5: revoke 후 해당 token inventory 제외 → `test_migrate_null_daemon_after_revoke_token_excluded_from_inventory` PASSED
|
||||
|
||||
## Evidence
|
||||
|
||||
- Test:
|
||||
```
|
||||
tests/test_daemon_registry.py 19 passed (REQ-022: 5 new + 14 pre-existing)
|
||||
tests/test_null_daemon_migration.py 6 passed (REQ-023: AC-1~5 + candidate daemon)
|
||||
Total BAT-007 scope: 25 passed, 0 failed
|
||||
```
|
||||
- Full regression:
|
||||
```
|
||||
.venv/bin/python -m pytest -q
|
||||
266 passed in 7.86s
|
||||
```
|
||||
- Manual: CLI dry-run 동작 코드 경로 검토 완료; `create_app(repository_factory=...)` 패턴 기반 CLI 통합 테스트로 실 CLI 경로 커버 확인
|
||||
- Logs: 없음 (DB 불필요 단위/CLI 테스트)
|
||||
|
||||
## Result
|
||||
|
||||
- Ready for Release: true
|
||||
- Blocking Issues: 없음
|
||||
@@ -13,3 +13,5 @@
|
||||
| BAT-003 | standard | release-candidate | REQ-001, REQ-002, REQ-003, REQ-006 | dcy-002 | [bat-003](./bat-003_20260519_tags-db-foundation/) |
|
||||
| BAT-004 | standard | release-candidate | REQ-004, REQ-005 | dcy-002 | [bat-004](./bat-004_20260520_tags-http-api/) |
|
||||
| BAT-005 | standard | release-candidate | REQ-007, REQ-008 | dcy-002 | [bat-005](./bat-005_20260520_pfhub-tag-cli-and-docs/) |
|
||||
| BAT-006 | standard | release-candidate | REQ-016, REQ-017, REQ-018 | dcy-004 | [bat-006](./bat-006_20260520_token-surface-code-fixes/) |
|
||||
| BAT-007 | standard | release-candidate | REQ-022, REQ-023 | dcy-004 | [bat-007](./bat-007_20260520_daemon-lifecycle-token-enforcement/) |
|
||||
|
||||
Reference in New Issue
Block a user