feat(bat-020): daemon-scoped 브로커 인증정보 (REQ-046~051)

- REQ-046: BrokerCredential.daemon_id 필드 추가, repository 시그니처 갱신
- REQ-047: alembic revision Step1(daemon_id NULL 컬럼 추가), Step2(PK 전환, dev/test DB 한정)
- REQ-048: backfill-daemon-id CLI subcommand (--dry-run/--force/--broker 지원)
- REQ-049: GET /v1/user_info daemon-scoped 단일 broker 전달 (brokers→broker, daemon_id 없으면 403)
- REQ-050: credential set/list/remove --daemon-id 옵션 추가 (daemon_slug 해석)
- REQ-051: 보안 격리 - service-token 전용, web 토큰 차단, 로그 app_secret 비노출, graceful degrade

비가역 결재 게이트: REQ-047 Step2(PK 전환)는 사장님 결재 후 실DB 적용 예정(이 batch는 revision 코드만)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
2026-06-28 00:45:46 +09:00
co-authored by Claude Sonnet 4.6
parent fc99f507a5
commit 51b7cbee35
16 changed files with 1878 additions and 86 deletions
+4 -3
View File
@@ -120,7 +120,8 @@ def test_user_info_returns_daemon_id_in_response(monkeypatch) -> None:
assert resp.json()["daemon_id"] == "d-1"
def test_user_info_null_daemon_id_returns_200_with_null(monkeypatch) -> None:
def test_user_info_null_daemon_id_returns_403(monkeypatch) -> None:
"""REQ-049 AC-3: daemon_id=None service token → 403 Forbidden (BAT-020 정책 변경)."""
client = _app_with_db(monkeypatch)
principal = _principal(daemon_id=None)
user = User(id="user-1", username="alice", permission="read", is_active=True)
@@ -134,8 +135,8 @@ def test_user_info_null_daemon_id_returns_200_with_null(monkeypatch) -> None:
patch("app.api.v1_user_info.get_portfolio_repository_from_settings", return_value=mock_repo):
resp = client.get("/v1/user_info", headers=_svc_header())
assert resp.status_code == 200
assert resp.json()["daemon_id"] is None
# REQ-049: daemon_id 없는 service token은 user_info 403 (daemon-scoped 인가)
assert resp.status_code == 403
# ---------------------------------------------------------------------------