# 0. 문서 상태 이 문서의 현재 상태와 식별 정보를 기록한다. - 상태: confirmed - 문서 ID: sdlc-002_20260423_service-token-user-based - 이슈명: service-token user-based 소유권 - 참조 Discovery: docs/sdlc/sdlc-002_20260423_service-token-user-based/1_discovery.md - 작성 시각(KST): 2026-04-23 - 마지막 갱신 시각(KST): 2026-04-23 (confirm-planning 실행 — Planning 최종 승인, Design 진입 허가) # 1. Discovery 인계 요약 Discovery에서 확정된 핵심 내용을 요약한다. 원본은 1_discovery.md를 참조하며, 이 섹션은 Planning 작업의 입력 기준선이 된다. ## 이슈 목적 `bot_principals` 테이블과 audit log를 제거하고, service token을 `auth_tokens` 단일 엔티티로 직접 발급하도록 재구현한다. CLI 명령 `pfhub service-token issue --user `으로 사용자 기반 소유권을 명확히 한다. ## 확정 범위 - In Scope - `bot_principals` 테이블 및 관련 코드 완전 제거 (도메인/스키마/서비스/리포지터리/API/CLI, 약 15개 파일, DB 마이그레이션 포함) - `auth_audit_logs` 테이블 및 audit 함수(`build_audit_event`, `record_audit_event`, `_record_audit_event`) 삭제 - `auth_tokens.token_type = "bot"` → `"service"` rename (기존 데이터 UPDATE 포함) - `auth_tokens.user_id` 소유권 보정 및 `bot_principal_id` 컬럼 DROP (마이그레이션) - service token 직접 발급 CLI 구현 (`pfhub service-token issue --user `) - `AuthContext.token_type` 필드 추가, service token 가드 교체 - 기존 테스트 업데이트 및 신규 검증 테스트 추가 - 기술 문서(`api-contracts.md`, `auth-and-token-lifecycle.md`, `README.md`) 업데이트 - Out of Scope - 외부 IAM(OAuth/OIDC) 연동 - service_principal 셀프서비스 UI/포털 제공 - token 권한 모델(read/trade-write/admin) 변경 - API 경로 변경에 대한 하위 호환성 레이어(alias route) 제공 — 외부 클라이언트 없으므로 즉시 제거 ## 기능 요구사항 (FR) 요약 - FR-1: `bot_principals` 테이블, `BotPrincipal` 모델, 관련 스키마/서비스/리포지터리/API, `auth_tokens.bot_principal_id` 컬럼, `auth_audit_logs` 테이블, audit 함수 전체 제거 - FR-2: service 토큰 발급 시 `auth_tokens.user_id`에 username에서 조회한 실제 user_id 직접 저장 - FR-3: 기존 bot 토큰 `user_id` → `bot_principal.owner_user_id`로 마이그레이션 보정 후 `bot_principals` DROP - FR-4: `get_token_principal()` COALESCE/JOIN 제거 → `t.user_id` 단순 참조 - FR-5: service token `name` 유니크, 중복 시 오류 반환 - FR-6: `--user ` → users 테이블 조회 → `auth_tokens.user_id`로 저장, 존재하지 않는 username이면 오류 - FR-7: `--user` 필수 옵션, 생략 시 오류 ## 비기능 요구사항 (NFR) 요약 - NFR-1: 변경 전후 모든 테스트 통과 (pytest 기준) - NFR-2: DB 마이그레이션 upgrade/downgrade 양방향 지원 - NFR-3: 변경 완료 후 CLI help 텍스트 및 오류 메시지에서 "bot" 잔류 참조 없음 ## 이월된 오픈 질문 / 리스크 Discovery에서 Deferred 처리된 항목 없음. 모든 OQ 해소 완료 (OQ-1 Answered, OQ-2/3/5 Dropped, OQ-4 Answered). # 2. 작업 분해 (Task Breakdown) FR/NFR 각각을 실행 가능한 단위 태스크로 분해한다. | ID | 태스크 제목 | 유형 | 참조 요구사항 | 의존 태스크 | 담당자 | |-----|-----------|------|--------------|------------|--------| | T-1 | DB 마이그레이션 파일 작성 | 개발 | FR-3, NFR-2 | - | copilot | | T-2 | 도메인/스키마/인터페이스 정리 | 개발 | FR-1 | - | copilot | | T-3 | postgres.py 구현체 정리 | 개발 | FR-1, FR-4 | T-2 | copilot | | T-4 | AuthContext/security.py 변경 | 개발 | FR-1 (OQ-4) | T-2 | copilot | | T-5 | AuthService 재구현 | 개발 | FR-2, FR-5, FR-6, FR-7 | T-3, T-4 | copilot | | T-6 | API 경로 정리 | 개발 | FR-1 | T-5 | copilot | | T-7 | CLI 재구현 | 개발 | FR-5, FR-6, FR-7, NFR-3 | T-5 | copilot | | T-8 | 테스트 업데이트 | 개발 | NFR-1 | T-5, T-6, T-7 | copilot | | T-9 | 기술 문서 업데이트 | 문서 | - | T-7, T-8 | copilot | ## 태스크 완료 조건 (DoD) - T-1: `alembic upgrade head` 실행 시 `bot_principals`, `auth_audit_logs` 테이블이 DROP되고, `auth_tokens.user_id`가 users FK를 정상 참조하며, `token_type = 'service'`로 UPDATE됨. `alembic downgrade -1` 실행 시 역방향 복원 가능. - T-2: `BotPrincipal`, `AuthToken.bot_principal_id`, `TokenPrincipal.bot_principal_id`, `CreateBotPrincipalRequest`, `BotPrincipalResponse`, `BotTokenIssueCliResponse.bot_id`, `BotTokenRevokeCliResponse.bot_id` 삭제/정리 완료. `PortfolioRepository` 프로토콜에서 bot_principal 메서드 제거. `python -c "from app.domain.models import AuthToken"` import 오류 없음. - T-3: `ensure_schema()` 재실행 시 `bot_principals`, `auth_audit_logs` 테이블 미생성. `get_token_principal()`이 단일 테이블 `auth_tokens`만 참조. `record_audit_event()` 메서드 삭제 완료. - T-4: `AuthContext`에 `token_type: str | None` 필드 추가 완료. `_resolve_auth_context()`가 DB 토큰 조회 시 `principal.token_type`을 전달. `build_audit_event()` 함수 삭제 완료. - T-5: `issue_service_token(name, username, ...)` 메서드가 존재하고, username → user_id 조회 후 `auth_tokens` 직접 발급. bot_principal 관련 메서드 전체 삭제. `issue_client_token`, `list_tokens`, `revoke_token`의 가드가 `auth_context.token_type == "service"` 체크로 교체됨. - T-6: FastAPI 앱 기동 후 `/auth/bots`, `/auth/me/bots` 경로 미노출. `curl /openapi.json`에서 해당 경로 없음. - T-7: `pfhub service-token issue --help`에서 ``, `--user` 인수 노출. help 텍스트에 "bot" 문자열 없음. `pfhub service-token issue kis-token --user justin` 명령이 토큰을 발급하고 출력. - T-8: `pytest` 실행 결과 0 failures, 0 errors. service token 발급/폐기 시나리오 테스트 신규 추가. - T-9: `auth-and-token-lifecycle.md`, `api-contracts.md`, `management-cli-operations.md`, `README.md`에서 "bot_principal", "bot token" 잔류 참조 없음. `pfhub service-token` 명령 예제 포함. # 3. 공수 추정 | ID | 태스크 제목 | 추정 공수 | 추정 단위 | 비고 | |-----|-----------|---------|---------|------| | T-1 | DB 마이그레이션 파일 작성 | 2 | SP | user_id 보정 로직 + downgrade 구현 포함 | | T-2 | 도메인/스키마/인터페이스 정리 | 2 | SP | 다수 파일이나 삭제 위주 — 복잡도 낮음 | | T-3 | postgres.py 구현체 정리 | 2 | SP | ensure_schema DDL 제거 + get_token_principal 단순화 — 주의 필요 | | T-4 | AuthContext/security.py 변경 | 1 | SP | 변경 범위 소규모 | | T-5 | AuthService 재구현 | 3 | SP | username 조회 신규 로직 + 가드 교체 + bot 메서드 삭제 | | T-6 | API 경로 정리 | 1 | SP | 삭제 위주 | | T-7 | CLI 재구현 | 2 | SP | 파일 rename + 인수 변경 + 출력 함수 rename | | T-8 | 테스트 업데이트 | 3 | SP | 4개 파일 업데이트 + 신규 시나리오 추가 | | T-9 | 기술 문서 업데이트 | 1 | SP | 내용 갱신 위주 | - 총 추정 공수: 17 SP - 추정 신뢰도: Medium - 추정 근거: sdlc-001(CLI bot token 발급 구현, 약 8 SP)과 유사한 레이어 범위이나 삭제 중심 + 기존 테스트 보정 작업이 추가됨. T-5/T-8이 가장 불확실 요소. # 4. 일정 계획 ## 마일스톤 | 마일스톤 | 목표 날짜 | 포함 태스크 | 완료 조건 | |---------|---------|-----------|---------| | M-1: 정리 완료 | 2026-04-25 | T-1, T-2, T-3, T-4 | bot_principals/auth_audit_logs 관련 코드·스키마·DDL 전체 삭제, import 오류 없음 | | M-2: 구현·검증 완료 | 2026-04-30 | T-5, T-6, T-7, T-8, T-9 | pytest 0 failures, CLI --help 정상 노출, 문서 갱신 완료 | ## 이터레이션 계획 - 이터레이션 단위: 없음(단일 배포) - 이터레이션 1 (2026-04-23 ~ 2026-04-24): T-1, T-2 (병렬 가능) - 이터레이션 2 (2026-04-24 ~ 2026-04-25): T-3, T-4 (T-2 완료 후) - 이터레이션 3 (2026-04-25 ~ 2026-04-26): T-5 (T-3, T-4 완료 후) - 이터레이션 4 (2026-04-27): T-6, T-7 (병렬 가능, T-5 완료 후) - 이터레이션 5 (2026-04-28 ~ 2026-04-30): T-8, T-9 (T-6, T-7 완료 후) # 5. 의존성 및 제약 ## 태스크 간 의존성 - T-1은 T-2와 독립적으로 병렬 진행 가능 - T-3, T-4는 T-2 완료 후 시작 가능 (인터페이스 정리 선행 필요) - T-5는 T-3, T-4 완료 후 시작 가능 (repository 메서드, AuthContext 변경 선행 필요) - T-6, T-7은 T-5 완료 후 병렬 시작 가능 - T-8은 T-5, T-6, T-7 완료 후 시작 가능 - T-9는 T-8 완료 후 시작 (최종 동작 확인 후 문서화) ## 외부 의존성 - DB 접속 가능 환경 필요 (마이그레이션 실행 및 S-3 검증) - pytest 실행 환경 (Docker Compose 또는 로컬 DB) ## 기술/환경 제약 - Alembic 마이그레이션 파일명 패턴: `YYYYMMDD_NNNN_.py` (기존 패턴 준수) - `ensure_schema()`는 테스트 환경에서도 호출되므로 DDL 제거 후 테스트 환경에서 재실행 검증 필요 - `app/cli/commands/bot_tokens.py` → `service_tokens.py` 파일 rename 시 `app/cli/main.py`의 import 경로도 함께 수정 필요 # 6. 검증 계획 개요 Discovery의 성공 기준과 연결해 검증 방법 초안을 적는다. | 성공 기준 | 검증 방법 초안 | 담당자 | |---------|-------------|--------| | S-1: pytest 0 failures | `pytest` 전체 실행 후 결과 확인 | copilot | | S-2: pfhub service-token --help 정상 노출 | 터미널에서 `pfhub service-token issue --help` 실행, `` 및 `--user` 인수 확인 | copilot | | S-3: auth_tokens.user_id FK 정합성 | 마이그레이션 upgrade 후 `SELECT COUNT(*) FROM auth_tokens WHERE user_id NOT IN (SELECT id FROM users)` = 0 확인 | copilot | # 7. 리스크 ## 리스크 - R-1 (High): 기존 `auth_tokens`에 `user_id = bot_principal.id`인 행의 `bot_principal.owner_user_id`가 NULL이거나 `users` 테이블에 없으면 마이그레이션 FK 위반 발생. - 완화 방안: T-1 구현 시 upgrade 전 사전 검증 쿼리(`SELECT COUNT(*) FROM bot_principals WHERE owner_user_id IS NULL`) 포함. NULL 존재 시 오류 후 중단. - R-2 (Medium): 약 15개 파일 rename 중 일부 누락 시 런타임 `AttributeError`/`NameError`. - 완화 방안: T-8 완료 후 `grep -r "bot_principal\|BotPrincipal\|bot_token\|token_type.*bot"` 잔류 참조 검사. 테스트 전체 통과를 gate로 설정. - R-3 (Medium): ensure_schema() DDL 제거 누락 시 재실행 때 bot_principals 테이블 재생성. - 완화 방안: T-3 DoD에 ensure_schema() 재실행 검증 포함. ## 가정 - PA-1: 현재 운영 중인 bot 토큰은 모두 `owner_user_id`가 설정된 `bot_principal`에 연결되어 있다 (migration 0011 backfill 완료). user_id 보정 후 DROP이 안전하다. - PA-2: `app/cli/main.py`의 `name="service-token"` 등록은 이미 완료되어 있으므로 T-7에서 import 경로만 수정하면 된다. - PA-3: 테스트 환경에서 Docker Compose DB 접속이 가능하다. ## 오픈 질문 없음. Discovery에서 모든 OQ 해소 완료. # 8. Design으로 넘기기 전 확인 체크 - [x] 전체 태스크 목록 확정 및 담당자 지정 - 기준: T-1 ~ T-9 모든 태스크에 담당자(copilot) 지정 완료 - [x] 공수 추정 완료 및 총 공수 확인 - 기준: 17 SP — 모든 태스크 추정값 채움 완료 - [x] 마일스톤 및 이터레이션 계획 확정 - 기준: M-1 목표 2026-04-25, M-2 목표 2026-04-30, 이터레이션 1~5 기간 확정 완료 - [ ] 외부 의존성 해소 계획 확인 - 기준: DB 접속 환경 및 pytest 실행 환경 확인 - [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - 기준: 없음 — Discovery에서 전체 해소 완료 - [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정 - 기준: 담당자 Justin 지정 완료 — 문서 서명 대기 # 9. 파일 처리 결과 - 처리 결과: 생성 - 생성 경로: docs/sdlc/sdlc-002_20260423_service-token-user-based/2_planning.md - 참조 문서: - docs/sdlc/sdlc-002_20260423_service-token-user-based/1_discovery.md - app/domain/models.py - app/core/auth.py - app/core/security.py - app/services/auth_service.py - app/repositories/postgres.py - app/repositories/portfolio_repository.py - app/schemas/auth.py - app/api/auth_routes.py - app/cli/commands/bot_tokens.py - app/cli/core/output.py # 10. 사용자 결정 필요 항목 요약 ## DECIDE - (없음) — Discovery 범위에서 모든 설계 결정 완료 ## CONFIRM - T-1 ~ T-9 담당자 지정: `copilot`으로 일괄 지정 완료 (run-sdlc 자동 처리) - M-1 목표 날짜: `2026-04-25`, M-2 목표 날짜: `2026-04-30` — 확정 완료 - 검증 담당자(`VERIFY_OWNER_1~3`): 지정 필요 (현재 TBD 유지) - Freeze 확정 담당자(`CONFIRMED_BY`): Justin — confirm-planning 단계에서 처리 ## DATA - 마이그레이션 실행 전 운영 DB `bot_principals` 행 수 및 `owner_user_id NULL` 행 수 확인 필요 (R-1 완화) # 11. Planning Freeze ## 섹션 참조 - 태스크 분해: [# 2. 작업 분해](#2-작업-분해) - 일정 계획: [# 4. 일정 계획](#4-일정-계획) - 리스크: [# 7. 리스크](#7-리스크) ## Handoff Decision - 승인 ## Ready for Design - true ## Confirmed By - Justin ## Confirmed At (KST) - 2026-04-23 ## Handoff Rationale - T-1~T-9 전체 태스크 분해 완료, 담당자 copilot 일괄 지정. - M-1(2026-04-25), M-2(2026-04-30) 마일스톤 일정 확정 및 5개 이터레이션 계획 수립. - Discovery FR-1~FR-7, NFR-1~NFR-3 전체 태스크 맵핑 확인, OQ 전체 해소 확인. - R-1 (High) 마이그레이션 FK 위반 리스크 완화 방안(T-1 사전 검증 쿼리) DoD에 포함. - 성공 기준 S-1~S-3 모두 검증 방법 정의 완료, 보과 플레이스홀더 없음. # 11. Planning Freeze ## 섹션 참조 - 작업 분해: [# 2. 작업 분해](#2-작업-분해-task-breakdown) - 공수/일정: [# 3. 공수 추정](#3-공수-추정), [# 4. 일정 계획](#4-일정-계획) - 의존성: [# 5. 의존성 및 제약](#5-의존성-및-제약) - 검증 계획: [# 6. 검증 계획 개요](#6-검증-계획-개요) - 리스크/오픈 질문: [# 7. 리스크](#7-리스크) - 인계 전 확인: [# 8. Design으로 넘기기 전 확인 체크](#8-design으로-넘기기-전-확인-체크) - 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) ## Freeze 플래그 - Handoff Decision: 보류 - Handoff Rationale: 담당자(TASK_OWNER_1~9), 마일스톤 날짜(MILESTONE_1_DATE, MILESTONE_2_DATE), 이터레이션 기간(ITERATION_1~5_PERIOD)이 TBD 상태. 확정 후 인계 가능. - Ready for Design: false - Confirmed By: {{CONFIRMED_BY:TBD}} - Confirmed At (KST): {{CONFIRMED_AT_KST:TBD}}