Files
portfolio-hub/docs/batches/bat-019_20260627_assets-read-permission-and-daemon-list/design.md
T
genichinandClaude Sonnet 4.6 c5288a8c42 feat(bat-019): REQ-044 GET /v1/assets require_read + REQ-045 GET /v1/daemons caller-scope
REQ-044: list_catalog_assets 의존성을 require_admin → require_read로 완화.
asset 쓰기(POST/PATCH/DELETE)는 require_admin 유지.

REQ-045: GET /v1/daemons 신규 엔드포인트 — caller 소유 데몬만 반환(require_read).
user_id=None(env 토큰)은 빈 목록 200. DaemonResponse/DaemonListResponse 스키마 추가.

기존 테스트(test_v1.py, test_asset_catalog.py, test_daemon_assignment.py, test_tags_master.py)
REQ-044 권한 완화에 맞게 업데이트. test_v1_daemons.py 신규 추가(13 케이스).

pytest 472 passed, 0 failed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
2026-06-27 10:36:04 +09:00

4.4 KiB

BAT-019 Design

  • Status: confirmed
  • Batch: BAT-019

Architecture Summary

이 batch는 두 가지 독립적인 변경으로 구성된다.

  1. REQ-044: GET /v1/assets의 인증 게이트를 require_admin → require_read로 완화. 기존 AssetCatalogListResponse 응답 구조, 필터 파라미터, 라우터 등록은 모두 불변.

  2. REQ-045: GET /v1/daemons 신규 엔드포인트. caller(auth.user_id) 소유 데몬만 반환. require_read 인증. 신규 파일: app/api/v1_daemons.py, 신규 스키마: DaemonResponse, DaemonListResponse. app/main.py에 라우터 등록.

Changed Areas

  • Docs: docs/batches/bat-019_20260627_assets-read-permission-and-daemon-list/
  • Modules:
    • app/api/v1_assets.py (require_admin → require_read, list_catalog_assets만)
    • app/api/v1_daemons.py (신규)
    • app/schemas/v1.py (DaemonResponse, DaemonListResponse 추가)
    • app/main.py (v1_daemons 라우터 include_router 추가)
  • Interfaces:
    • GET /v1/assets: 기존 인터페이스 불변, 인증 게이트만 완화
    • GET /v1/daemons: 신규 엔드포인트

Key Decisions

  1. REQ-044 — 권한 완화 범위: list_catalog_assets(GET /v1/assets) 1개만 require_read로 교체. POST /v1/assets, PATCH /{id}, PATCH /{id}/deactivate, POST /{id}/tags, DELETE /{id}/tags/{tid}는 require_admin 유지. 응답 스키마 AssetCatalogListResponse는 불변.

  2. REQ-045 — 경로: GET /v1/daemons. /v1/user/daemons 대신 /v1/daemons 채택 — 기존 /v1/tags, /v1/assets, /v1/portfolios 패턴과 일치.

  3. REQ-045 — 응답 스키마: DaemonListResponse{daemons: list[DaemonResponse], total: int}. DaemonResponse 필드: id(str), daemon_slug(str), display_name(str|None), is_active(bool). 기존 TagListResponse{tags: list[TagItem], total: int} 패턴과 동일 구조.

  4. REQ-045 — env/service 토큰(user_id=None) 처리: auth.user_id가 None이면 빈 목록 [] + total: 0을 HTTP 200으로 반환. 이유: 환경 변수 토큰 또는 user_id 없는 서비스 토큰은 특정 사용자 소유 데몬이 없으므로 에러보다 빈 응답이 자연스럽다. 기존 /v1/user_info의 env 토큰 처리(403 Service token required)와 다른 접근이며, 데몬 목록은 사용자 식별 없이도 빈 목록을 반환하는 것이 호출자 친화적이다.

  5. REQ-045 — active 필터: 기본적으로 소유 데몬 전체 반환(active/inactive 모두). is_active 필드로 구분 표시. 이유: caller가 비활성 데몬도 인지해야 할 수 있으며(재활성화 등), 필터는 UI 레이어에서 처리 가능.

  6. REQ-045 — AuthService 활용: AuthService.find_candidate_daemons_for_user(user_id) 호출. 단, 라우터에서 직접 repository를 호출하는 기존 패턴(v1_assets, v1_tags 등)과 일관성을 위해 라우터에서 직접 repo.list_daemons_for_user(user_id)를 호출하는 방식 채택 (service 레이어 DI overhead 없이 기존 패턴 유지).

  7. REQ-044 테스트 영향: 기존 테스트 중 test_get_v1_assets_user_token_returns_403, test_get_v1_assets_service_token_returns_403는 이번 완화로 동작이 바뀜. — user 토큰(permission="read"): 이제 200이어야 함 → 테스트 업데이트. — service 토큰(permission="trade-write"): trade-write는 read 포함(_has_permission 로직상)이므로 200이어야 함 → 테스트 업데이트. 또한 test_v1_assets_rejects_env_token(test_v1.py): env read 토큰도 이제 200이어야 함 → 업데이트. test_v1_assets_returns_asset_list: read 토큰이 이제 403이 아닌 200 → 업데이트. test_v1_assets_market_filter, test_v1_assets_asset_id_is_uuid: 동일.

Edge Cases

  • user_id=None(env 토큰): 빈 목록 200 반환 (결정 #4).
  • 데몬 없는 사용자: 빈 목록 [] 200 반환.
  • admin 토큰도 caller 스코프: admin 토큰의 user_id가 None이면 빈 목록, user_id가 있으면 해당 user 소유 데몬만.
  • list_daemons_for_user DB 미활성(db_enabled=False): 빈 목록 반환 또는 InMemory 구현에 따름.

Architecture Impact

  • none (라우터 추가는 runtime-flows 수준 변경이 아님; project-structure에 신규 파일 기록 수준)

Reference Doc Update Plan

  • project-structure.md: none (파일 추가 수준, 별도 문서화 불필요)
  • runtime-flows.md: none