# BAT-010 Design - Status: confirmed - Batch: BAT-010 ## Architecture Summary daemon identity 모델을 도입해 service-token과 asset에 stable `daemon_id`를 부여하고, `/v1/assets`와 `/v1/assets/{asset_id}/ledger/trades`의 authorization 축을 user-scope에서 daemon-scope로 전환한다. 기존 service-token bearer auth와 permission 모델은 유지하고, daemon assignment는 그 위에 추가되는 두 번째 authorization 축이다. 전환 구간에서는 NULL backfill을 허용하고 fail-closed로 처리한다. ## Changed Areas - Docs: - `docs/interface-contract.md` — daemon binding, daemon-scoped contract, 403 mismatch policy - `docs/data-model.md` — daemons 테이블, auth_tokens.daemon_id, portfolios.managed_by_daemon_id - `docs/runtime-flows.md` — daemon registry binding 흐름, rotate invariant, daemon-scoped v1 API 흐름 - `README.md` — daemon provisioning runbook, enforcement enablement gate - Modules: - `migrations/versions/` — daemons, auth_tokens.daemon_id, portfolios.managed_by_daemon_id migration - `app/domain/models.py` — Daemon entity 추가 - `app/repositories/postgres.py` — daemon CRUD methods - `app/repositories/portfolio_repository.py` — daemon-scoped asset query - `app/services/auth_service.py` — daemon binding at issue/rotate - `app/api/auth_routes.py` (또는 신규 daemon route) — daemon registry management endpoint - `app/api/v1_assets.py` — daemon-scoped GET, daemon assignment auth for ledger write - `app/api/v1_user_info.py` — daemon_id 필드 노출 - `app/schemas/v1.py` — daemon_id 필드 추가 - `app/cli/commands/service_tokens.py` — daemon_id binding at issue, daemon_id invariant at rotate, daemon_id in output - Interfaces: - Daemon registry management: HTTP endpoint(`POST /daemons`, `GET /daemons`, `PATCH /daemons/{daemon_id}`) 또는 CLI subcommand(`pfhub daemon create/list/update`) — **아래 Key Decisions 참조** - `POST /auth/service-tokens` request body: `daemon_id` 필드 추가 - `GET /v1/assets` response: daemon-scoped 필터 - `GET /v1/user_info` response: `daemon_id` 필드 추가 - `POST /v1/assets/{asset_id}/ledger/trades`: daemon assignment 검증 추가 ## Key Decisions 1. **Daemon registry 관리 경로 형태**: CLI subcommand(`pfhub daemon create/list/update`)를 1차 관리 경로로 채택. 운영자 중심 도구이고, HTTP endpoint로 노출 시 별도 admin-only auth guard 설계가 필요하다. HTTP endpoint는 향후 필요 시 추가한다. 2. **daemon_slug uniqueness**: owner user 내에서 unique. DB unique constraint(`daemons.owner_user_id + daemons.daemon_slug` composite unique)로 강제. 3. **enforcement enablement gate**: migration level에서 별도 flag 없이 진행. NULL backfill token/asset에 대한 fail-closed는 코드 레벨에서 처리. production cutover는 inventory + dry-run + release approver 승인 후 수동으로 진행. 4. **NULL backfill fail-closed boundary**: `daemon_id = NULL`인 token이 daemon-scoped endpoint(`GET /v1/assets`, `POST /v1/assets/{asset_id}/ledger/trades`)를 호출하면 403 Forbidden. `GET /v1/user_info`는 NULL이어도 200 OK + `daemon_id: null` 진단 노출. ## Edge Cases - 동일 user 아래 daemon이 없는 상태에서 service-token 발급 시: daemon registry에 daemon이 없으면 발급 불가. 운영자가 먼저 daemon create를 수행해야 한다. - rotate 중 daemon이 삭제된 경우: daemon FK 참조 무결성 위반으로 rotate 거부. 소프트 삭제(is_active flag) 방식으로 daemon을 비활성화하는 것이 안전하다. - managed_by_daemon_id가 설정된 asset을 다른 daemon으로 재배정: 명시적 reassignment API/CLI 또는 migration-level 절차로만 허용. 자동 재배정 없음. ## Architecture Impact - runtime-flows ## Reference Doc Update Plan - project-structure.md: none (파일 구조 변경 없음) - runtime-flows.md: 갱신 필요 — daemon registry binding 흐름, daemon-scoped v1 API 처리 흐름, rotate `runtime_id`/`daemon_id` 역할 차이 반영 (REQ-011 범위)