Files
portfolio-hub/docs/releases/rel-003_20260628_daemon-credential-and-admin-api.md
T

14 KiB

REL-003: daemon-scoped 브로커 인증정보 + 관리 API (dcy-010 + dcy-011)

  • Status: released
  • Profile: app-service
  • Included Batch: BAT-018 (REQ-043), BAT-019 (REQ-044, REQ-045), BAT-020 (REQ-046051), BAT-021 (REQ-047 Step2 통합 및 런북), BAT-022 (REQ-052054)

Scope

Included

  • BAT-018 (dcy-008, batch-lite, release-candidate) — REQ-043: GET /v1/portfolios/{id}/holdings/cost-basis 엔드포인트 신설 (WAC/평단 조회)
  • BAT-019 (dcy-009, standard, confirmed) — REQ-044/045: GET /v1/assets 권한 완화(require_read) + GET /v1/daemons 신설
  • BAT-020 (dcy-010, standard, release-candidate) — REQ-046~051: BrokerCredential daemon_id 도메인 모델 + Step1 마이그레이션 + backfill CLI + /v1/user_info daemon-scoped 확장 + CLI --daemon-id 옵션 + 보안 격리
  • BAT-021 (dcy-010, standard, release-candidate) — REQ-047: Step2 동반 코드 수정(ensure_schema/upsert ON CONFLICT) + 실 PostgreSQL 통합검증 + Step2 런북 (docs/runbooks/bat-021-req047-step2-migration-runbook.md)
  • BAT-022 (dcy-011, standard, release-candidate) — REQ-052~054: 사용자 소유 스코프 인증정보 관리 API + admin 데몬 CRUD API + 인증정보 보안 격리(쓰기전용·secret 미반환·로그 미노출)
  • (동반 배포) dcy-035 WAC #15 (3a6adc9, REQ-041 change-req CHG-20260626-01) — /pnl/realized·holdings cost-basis의 매수 수수료를 WAC cost basis에 편입. 이미 origin/main 머지됨 → 이번 portfolio-hub 코드 배포(main)에 함께 실린다. trex-core 운영리포트(dcy-035, REL-010)와 짝을 이루는 멀티데이 WAC 정확값의 portfolio-hub 측. (별도 배포·재시작 불필요 — 동일 배포에 포함)

Excluded

  • REQ-047 Step2(PK 전환) 운영 DB 실제 적용 — 이번 릴리즈의 코드 배포 이전 ops 윈도우에서 별도 수행 (런북 docs/runbooks/bat-021-req047-step2-migration-runbook.md 엄수, 사장님 결재 게이트)
  • trex-core 소비자 어댑터 (KISConfig fetch 경로) — 별도 trex-core Discovery (dcy-010 out-of-scope)
  • trex-web UI (dcy-009) — trex-web repo 별도 진행
  • 서비스 토큰 발급·폐기 (dcy-011 out-of-scope)

전제 조건 (배포 시작 전 필수 확인)

  1. CREDENTIAL_ENCRYPTION_KEY — 운영 환경 env에 Fernet key 값이 설정돼 있어야 한다. 미설정 시 인증정보 암·복호화 전체가 동작하지 않는다.

    • 확인: 서비스 실행 환경에서 echo $CREDENTIAL_ENCRYPTION_KEY | head -c 10 (공백이면 미설정)
    • 최초 설정: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" 결과를 CREDENTIAL_ENCRYPTION_KEY에 등록
    • 주의: 기존 운영 인스턴스에 이미 설정돼 있으면 변경 금지 (기존 암호화 payload를 복호화할 수 없게 됨)
  2. Step2 마이그레이션 완료 확인 — 코드 배포 전에 REQ-047 Step2(alembic revision 20260628_0027) 적용이 완료되어야 한다. Step2 미적용 상태로 BAT-021 코드를 배포하면 upsert_broker_credential의 ON CONFLICT (daemon_id, broker) 절이 구 스키마(PK=(user_id, broker))와 맞지 않아 인증정보 등록/회전 실패.

    • 검증 쿼리: SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'broker_credentials_pkey';
    • 기대값: PRIMARY KEY (daemon_id, broker)
  3. alembic 상태 사전 확인 — 현행 운영 DB의 alembic head가 20260628_0027 (Step2 완료 후) 인지 확인:

    alembic current
    # 기대: 20260628_0027 (head)
    
  4. DB 백업 — Step2 마이그레이션 적용 전 운영 DB 전체 스냅샷 백업 (런북 1단계 참조).

  5. pytest 통과 확인 — main HEAD (9fe6f3b) 기준으로 pytest 전체가 통과해야 한다.

    • BAT-022 기준 565 passed + 핫픽스 커밋 포함 최신 스위트 통과 확인 필요.

Rollout Plan

배포 순서 엄수. 특히 Step2 미적용 상태에서 코드 배포하면 upsert 실패가 발생한다.

[ops 윈도우] DB 백업 → Step1 upgrade → backfill CLI → NULL 0건 확인
           → Step2 upgrade (사장님 결재 필수) → Step2 스키마 검증
[코드 배포]  → portfolio-hub 서비스 rolling restart
[smoke]     → 헬스체크 → 스모크 테스트 체크리스트 실행

단계별 상세

[ops 윈도우 전용 — 코드 배포 전 수행]

  1. DB 백업: 운영 DB 전체 스냅샷 (pg_dump) — 런북 1단계 참조
  2. Step1 upgrade (alembic upgrade 20260628_0026): broker_credentials에 daemon_id TEXT NULL 컬럼 추가. 기존 PK (user_id, broker) 유지.
  3. backfill CLI: pfhub credential backfill-daemon-id --user <username> --daemon-id <daemon_slug> — 기존 user-scoped 인증정보 행에 daemon_id 설정. 사용자별 반복. --dry-run 선 확인 권장.
  4. NULL 0건 검증: SELECT COUNT(*) FROM broker_credentials WHERE daemon_id IS NULL; → 반드시 0 확인. 1건이라도 있으면 Step2 진행 금지 (upgrade가 RuntimeError로 자동 거부함).
  5. Step2 upgrade (alembic upgrade 20260628_0027, 사장님 결재 필수): PK를 (user_id, broker) → (daemon_id, broker)로 전환. daemon_id NOT NULL 제약 추가. 비가역 작업.
  6. Step2 스키마 검증: PK 전환·daemon_id NOT NULL·user_id 유지를 SQL 쿼리로 확인 — 런북 4단계 검증 SQL 참조.

[코드 배포]

  1. portfolio-hub 서비스 rolling restart: freeze 커밋 9fe6f3b (main HEAD) 이미지로 교체.
    • 포함 변경 모듈: app/repositories/postgres.py (ensure_schema·upsert ON CONFLICT), app/api/v1_daemons.py (admin CRUD·daemon 목록), app/api/v1_credentials.py (소유 스코프 관리 API), app/api/v1_portfolios.py (holdings/cost-basis), app/api/v1_user_info.py (daemon-scoped broker 전달), app/cli/commands/credential.py (--daemon-id·backfill), app/domain/pnl.py (calculate_current_wac)

[배포 후 smoke]

  1. 아래 Verification Checklist 항목 순서대로 실행.

Rollback Plan

트리거 조건

  • GET /health HTTP 200 실패
  • GET /v1/user_info (service token) HTTP 500 또는 broker 필드 누락
  • 인증정보 PUT/GET 연속 500 발생
  • 기존 API(/v1/portfolios, /v1/assets, /v1/daemons) 회귀 발생

코드 롤백 (1순위)

BAT-022/021/020은 기존 기능의 additive 추가 및 REQ-049 동작 변경(brokers→broker 단일화)이 포함된다. 코드 롤백 시 REL-002 freeze 이미지(커밋 d89f137)로 복원하면 된다. DB는 Step2 이후 상태 그대로 둬도 무방 (코드가 구 스키마를 참조하지 않는 방향으로 하위호환 불가하여 DB 롤백 필요 여부를 판단).

portfolio-hub → REL-002 이미지(커밋 d89f137)로 rolling restart

DB 롤백 (Step2 롤백 — 조건부)

Step2 downgrade는 같은 user의 여러 daemon이 동일 broker를 쓰는 경우 RuntimeError로 자동 거부된다. 단일 daemon 1계좌 환경이면 downgrade 가능:

alembic downgrade 20260628_0026   # Step2 → Step1 상태
alembic downgrade 20260605_0025   # Step1 → 이전 상태 (daemon_id 컬럼 제거)

경고: Step2 downgrade는 조건부 가역이다. 백업 복원이 최후 수단.

pg_restore -h <DB_HOST> -U <DB_USER> -d <DB_NAME> -c backup_pre_bat021_<TIMESTAMP>.dump

롤백 영향 분석

  • REQ-049 (/v1/user_info daemon-scoped): brokers 리스트→단일 broker 변경. trex-core 소비자는 daemon_id만 읽으므로 무영향(소비자 인벤토리 확인 완료).
  • REQ-052/053: HTTP API 추가분 — additive. 롤백 시 엔드포인트 제거만 발생. 기존 운영 영향 없음.
  • REQ-043/044/045 (BAT-018/019): additive 추가·권한 완화. 롤백 시 신규 엔드포인트 제거 + GET /v1/assets가 require_admin으로 복원.

Verification Checklist

배포 전

  • Step2 완료 확인: SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'broker_credentials_pkey'; → PRIMARY KEY (daemon_id, broker)
  • alembic head 확인: alembic current → 20260628_0027 (head)
  • NULL 잔존 0건: SELECT COUNT(*) FROM broker_credentials WHERE daemon_id IS NULL; → 0
  • CREDENTIAL_ENCRYPTION_KEY 운영 환경 설정 확인
  • pytest 전체 통과 확인 (freeze 커밋 9fe6f3b 기준)
  • DB 백업 완료 확인 (백업 파일 경로/사이즈 기록)

배포 직후

기본 헬스

  • GET /health → HTTP 200, db_enabled: true

admin 데몬 CRUD (REQ-053 — BAT-022)

  • POST /v1/admin/daemons (admin-token) — 데몬 생성 → HTTP 201 + DaemonAdminResponse (id, owner_user_id, daemon_slug, is_active)
  • GET /v1/admin/daemons (admin-token) — 데몬 목록 → HTTP 200 + 데몬 목록 반환
  • GET /v1/admin/daemons/{daemon_id} (admin-token) → HTTP 200
  • PATCH /v1/admin/daemons/{daemon_id} (admin-token) — display_name 변경 → HTTP 200
  • PATCH /v1/admin/daemons/{daemon_id} (admin-token, is_active=false) — 비활성화 → HTTP 200 + is_active=false
  • DELETE /v1/admin/daemons/{daemon_id} (admin-token) → HTTP 405 (하드 삭제 금지)
  • 비admin user-token으로 admin CRUD 시도 → HTTP 403

인증정보 관리 API (REQ-052 — BAT-022)

  • PUT /v1/daemons/{daemon_id}/credential (user-token, 소유 데몬) → HTTP 200 + 메타데이터(account_ref, credential_version, last_rotated_at, broker) 응답; app_key/app_secret 미포함 확인 (필드 부재)
  • GET /v1/daemons/{daemon_id}/credential (user-token, 소유 데몬) → HTTP 200 + 메타데이터; secret 미포함 확인
  • DELETE /v1/daemons/{daemon_id}/credential (user-token, 소유 데몬) → HTTP 204
  • 타 사용자 user-token으로 소유 스코프 외 데몬 인증정보 조회/수정 → HTTP 403

service-token /v1/user_info daemon-scoped (REQ-049 — BAT-020)

  • service-token(daemon_id 바인딩)으로 GET /v1/user_info → broker 단일 객체(account_ref, credential_payload 포함), brokers 리스트 키 없음 확인
  • CREDENTIAL_ENCRYPTION_KEY와 일치하는 키로 인증정보를 등록한 경우 credential_payload 복호화 값(app_key 포함) 반환 확인
  • daemon_id 없는 service-token으로 /v1/user_info → HTTP 403
  • user-token으로 /v1/user_info → HTTP 403 (REQ-051 보안 격리 회귀)

WAC 조회 엔드포인트 (REQ-043 — BAT-018)

  • GET /v1/portfolios/{id}/holdings/cost-basis (read-token, 소유 portfolio) → HTTP 200 + items, cost_basis_from 포함

자산/데몬 목록 (REQ-044/045 — BAT-019)

  • GET /v1/assets (read-token, non-admin) → HTTP 200 (권한 완화 확인)
  • GET /v1/daemons (user-token) → HTTP 200 + 소유 데몬 목록

기존 API 회귀

  • GET /v1/portfolios (read-token 이상) → HTTP 200
  • POST /v1/portfolios/{id}/ledger/trades (trade-write-token) → 기존 동작 유지
  • GET /v1/portfolios/{id}/trades (read-token 이상) → HTTP 200
  • GET /v1/portfolios/{id}/pnl/realized (read-token 이상) → HTTP 200

Known Issues (릴리즈 전 인지 사항)

  1. BAT-022 AC-3 통합 수준: REQ-052 AC-3(CLI·API·service-token 3경로 정합)이 unit-level 검증. 실 DB 통합 환경에서 재확인 권장(비가역 아님, 선택적).
  2. owner 재할당 원자성 (해소됨 — 오기 정정): update_daemon_owner()는 단일 conn.transaction() 트랜잭션으로 credential DELETE(선행) → owner UPDATE를 원자 처리한다(BAT-022 QC Major fix, 커밋 83e6dde). 어느 한쪽 실패 시 전체 롤백되어 "owner만 바뀌고 구 인증정보 잔존"하는 leak 상태가 발생하지 않는다. 라이브 통합검증(2026-06-28)에서 owner 재할당 → 인증정보 무효화 PASS. 리스크 아님.
  3. DELETE credential broker 고정: DELETE /v1/daemons/{daemon_id}/credential은 broker="kis" 고정. 다중 broker 지원 시 별도 설계 필요(현재 단일 broker 가정 설계).
  4. REL-002 Known Issues 이월: symbol 필터 edge 테스트, HTTP_422 deprecated 경고, 성능 미검증 — 여전히 비차단 잔여.

Release Execution Evidence

  • Release Status Transition Target: released

Deployment Facts

  • Executed At:
    • 2026-06-30 (M3 배포 윈도우)
  • Completed At:
    • 2026-06-30
  • Environment:
    • portfolio-hub 운영 인스턴스
  • Operator:
    • genichin (ops 윈도우 수행)
  • Revision or Artifact:
    • 배포 커밋: 9d98d47 (origin/main code freeze; 마이그레이션 0026/0027 + 크레덴셜 백엔드 + WAC #15 포함)
    • alembic head (배포 후 확인): 20260628_0027 (head) — Step1→backfill→Step2 적용 완료, broker_credentials PK (daemon_id, broker) 전환, daemon_id NOT NULL, row 보존
  • Commands or Procedure:
    • 상세는 위 Rollout Plan 참조
    • DB 마이그레이션: docs/runbooks/bat-021-req047-step2-migration-runbook.md 엄수
    • 사전 백업: /root/release/docs/backup_pre_bat021_20260630123909.dump
    • backfill 적용: pfhub credential backfill-daemon-id --user genichin --daemon-id trex-genichin-longterm (중장기 데몬 신설)
    • 코드 배포: portfolio-hub 서비스 rolling restart (freeze 커밋 9d98d47)

Profile-Specific Checks

  • app-service:
    • Health Endpoint:
      • GET /health → HTTP 200, db_enabled: true PASS
    • Startup Log Fields:
      • 정상 기동 확인
    • Request Log Fields:
      • 정상 응답 확인
    • Smoke or Manual Verification:
      • GET /health → HTTP 200 (db_enabled=true) PASS
      • pfhub daemon list --user genichin → exit 0 PASS
      • pfhub credential list --user genichin --daemon-id trex-genichin-longterm → exit 0 PASS

Rollback Outcome

  • Rollback Required:
    • 없음
  • Rollback Trigger:
    • 해당 없음
  • Rollback Action:
    • 해당 없음

Release Decision Evidence

  • Released Decision:
    • released
  • Rationale:
    • 배포 리비전 9d98d47 기준 alembic head 20260628_0027 적용 완료. 스모크 체크(/health, daemon list, credential list) 전항목 PASS. 롤백 트리거 없음.
  • Recorded By:
    • 팀장 (M3 배포 완료 운영팀 확인 기반)
  • Recorded At:
    • 2026-06-30

Feedback Handoff

  • Observation Summary:
    • (배포 후 운영 관찰 기입)
  • Discovery Input:
    • trex-core 소비자 어댑터 (KISConfig fetch, dcy-010 out-of-scope) — 별도 trex-core Discovery (dcy-036/BAT-054로 진행, PR #35)
  • REQ Input:
    • (배포 후 기입)
  • Change Request Input:
    • (배포 후 기입)