# BAT-020 Planning ## 구현 의존 순서 1. **REQ-046** — BrokerCredential 도메인 모델 + repository 시그니처 변경 (daemon_id 필드) 2. **REQ-047 Step1** — alembic revision: broker_credentials에 daemon_id TEXT NULL 컬럼 추가 3. **REQ-048** — backfill-daemon-id CLI subcommand 추가 4. **REQ-047 Step2** — alembic revision: PK (user_id,broker) → (daemon_id,broker) 전환 (dev/test DB만) 5. **REQ-049** — GET /v1/user_info daemon-scoped 단일 broker 전달 (스키마 + 엔드포인트) 6. **REQ-050** — credential CLI --daemon-id 옵션 추가 (set/list/remove) 7. **REQ-051** — 보안 격리 테스트: service-token 전용, web 토큰 차단, 로그 비노출 검증 ## Task Breakdown ### T-1: REQ-046 모델·repository 변경 - `app/domain/models.py` — `BrokerCredential.daemon_id: str | None = None` 추가 - `app/repositories/postgres.py` — `_row_to_broker_credential` daemon_id 처리 (7컬럼/6컬럼 하위호환) - `app/repositories/postgres.py` — `upsert_broker_credential(daemon_id=None)` 파라미터 추가 - `app/repositories/postgres.py` — `list_broker_credentials(daemon_id=None)` 파라미터 + 필터 추가 - `app/repositories/postgres.py` — `delete_broker_credential(daemon_id=None)` 파라미터 추가 - Tests: REQ-046 AC-1~6 단위 테스트 신규 ### T-2: REQ-047 Step1 alembic revision - `migrations/versions/20260628_0026_broker_credential_daemon_id_step1.py` - 내용: `broker_credentials`에 `daemon_id TEXT NULL` 컬럼 추가 + downgrade(컬럼 제거, PK 복원) - Tests: AC-1 (컬럼 존재), AC-2 (기존 데이터 NULL 보존), AC-6 (downgrade) ### T-3: REQ-048 backfill CLI - `app/cli/commands/credential.py` — `backfill-daemon-id` subcommand 추가 - Tests: AC-1~6 단위 테스트 (mock repo 기반) ### T-4: REQ-047 Step2 alembic revision - `migrations/versions/20260628_0027_broker_credential_daemon_id_step2.py` - 내용: PK 전환 (user_id,broker) → (daemon_id,broker), daemon_id NOT NULL 제약, downgrade 안전장치 - `app/repositories/postgres.py` — `ensure_schema()` broker_credentials 정의 최종 스키마로 갱신 (Step2 이후 활성화 주석) - Tests: AC-3~5 (dev DB에서 Step1→백필→Step2 순서 검증), AC-7 (downgrade 안전장치) ### T-5: REQ-049 user_info daemon-scoped 확장 - `app/schemas/v1.py` — `UserInfoResponse.broker: BrokerItem | None = None`, `brokers: list` 제거 - `app/api/v1_user_info.py` — daemon_id 없으면 403, daemon_id 기반 list_broker_credentials 호출, 단일 broker 반환 - Tests: REQ-049 AC-1~9 (daemon_id 있음/없음, broker 있음/없음, 2데몬 격리, 복호화 실패, 회귀) ### T-6: REQ-050 credential CLI --daemon-id - `app/cli/commands/credential.py` — set/list/remove 에 `--daemon-id` 옵션 추가 - `tests/test_cli.py` FakeRepo — upsert/list/delete에 daemon_id 파라미터 반영 - Tests: REQ-050 AC-1~7 ### T-7: REQ-051 보안 격리 테스트 - web 토큰 403 (AC-1), 비인증 401 (AC-2), daemon_id=None 403 (AC-3), 타데몬 null (AC-4) - 타 엔드포인트 credential 미노출 (AC-5) - DB 오류 graceful degrade (AC-6), 복호화 실패 degrade (AC-7) - 로그 비노출 (AC-8, caplog fixture) ## 파일 변경 대상 요약 | 파일 | 변경 내용 | | --- | --- | | `app/domain/models.py` | BrokerCredential.daemon_id 추가 | | `app/repositories/postgres.py` | _row_to_broker_credential, upsert/list/delete 시그니처, ensure_schema broker_credentials | | `app/schemas/v1.py` | UserInfoResponse brokers→broker 단일화 | | `app/api/v1_user_info.py` | daemon-scoped 조회, daemon_id 없음 403, 로그 비노출 확인 | | `app/cli/commands/credential.py` | set/list/remove --daemon-id, backfill-daemon-id subcommand | | `migrations/versions/20260628_0026_*.py` | Step1 revision | | `migrations/versions/20260628_0027_*.py` | Step2 revision | | `tests/test_v1.py` | user_info 테스트 갱신 + 신규(daemon-scoped, 보안) | | `tests/test_cli.py` | credential CLI 테스트 갱신 + 신규 | | `tests/test_bat020_broker_credential_domain.py` | REQ-046 단위 테스트 신규 | | `tests/test_bat020_broker_credential_migration.py` | REQ-047 마이그레이션 테스트 신규 | ## 위험 - REQ-049: `brokers` → `broker` 응답 변경으로 기존 `test_v1.py`의 `brokers` 필드 참조 테스트가 실패할 수 있음 → 갱신 필수 - REQ-047 Step2: 실 DB 적용 금지(게이트). dev/test DB에서만 검증. - `get_daemon_by_slug`은 `owner_user_id` + `daemon_slug` 조합으로 조회 → REQ-050에서 daemon_slug로 받으면 user 조회 먼저 필요