POST /v1/portfolios now forwards a non-null managed_by_daemon_id into
create persistence, and both create and PATCH apply the same daemon
validation contract (UUID format -> 422, missing/foreign -> 404,
inactive -> 409, admin target-owner basis). Daemon validation locks the
daemon row FOR SHARE inside the portfolio write transaction so
validation and write form one atomic decision boundary.
Adds domain errors, repository protocol/signature updates, API/PostgreSQL
tests (POST->GET persistence, no partial state on failure, assignment vs
deactivation race), and contract/data-model documentation.
Add reversal-aware grouped manual-ledger aggregates to portfolio list responses and align the single-portfolio return summary with the same semantics.\n\nRefs #23
Adds 4 additive fields to GET /v1/portfolios (and single-portfolio) response:
- active_strategy_id (str|null): portfolios.active_strategy_id raw value
- strategy_name (str|null): strategy_sets.name via LEFT JOIN
- strategy_params (dict): strategy_sets.config JSON parsed; {} on missing/invalid
- markets (list[str]): assets.markets; [] on missing/empty
_SELECT_PORTFOLIO extended with LEFT JOIN strategy_sets; _row_to_portfolio
parses new columns (row[19-21]) with JSON error isolation (WARNING log, no 500).
_portfolio_to_item maps all 4 new fields. Existing 16 fields unchanged.
N+1 prevented: single SQL JOIN for both list and single-item paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
update_daemon_owner()에서 owner UPDATE + credential DELETE를 단일
conn.transaction() 블록으로 묶어 원자성 보장. 실패 시 두 연산 모두
롤백되므로 "새 owner + 이전 credential 잔류" leak 상태(dcy-011 OQ-3)
가 불가능해짐. 실행 순서도 DELETE-first(안전 순서)로 고정.
단위 테스트 3종 추가:
- DELETE-before-UPDATE 실행 순서 검증
- UPDATE 실패 시 rollback(commit 미호출) 검증
- 단일 transaction() 블록 안에서 두 연산 실행 검증
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
- GET /v1/portfolios/{id}/trades and GET /v1/portfolios/{id}/pnl/realized endpoints
- Weighted Average Cost (WAC) realized PnL calculation in app/domain/pnl.py
- cost_basis and data_completeness_warning returned in pnl API response
- Test coverage: 50 passed (WAC 20 + trades 12 + pnl 18), full 427 passed regression ✓
- WAC validated against 199,300 unit e2e test cases
- DB migration: none required
- QC: qc-reviewer PASS-WITH-NOTES; team lead confirm-batch-verification → release-candidate
- Remaining non-blocking: symbol filter edge case testing, HTTP_422 deprecation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
trex-core reported GET /v1/user_info returning 500 for valid service/daemon
tokens (whoami → daemon_id), degrading the daemon and holding new trades. The
reported hypothesis (service-token None deref) was a misdiagnosis. Root cause:
app/api/v1_user_info.py: credentials = repo.list_broker_credentials(...)
psycopg.errors.UndefinedTable: relation "broker_credentials" does not exist
The broker_credentials table was missing and the handler only caught
AttributeError, so the DB error propagated to a bare 500.
Two fixes:
1. Handler now degrades gracefully when broker-credential lookup fails — returns
empty brokers and still serves daemon_id/user_id/permission, which do not
depend on broker credentials. This is what trex-core's whoami needs.
2. ensure_schema() now also CREATE TABLE broker_credentials (same shape as
migration 20260502_0014). ensure_schema-bootstrapped DBs were missing it,
since previously only the alembic path created it.
Added tests/test_v1.py regression: broker-credential lookup raising a DB error
must yield HTTP 200 with daemon_id, not 500. Verified against the real DB
(was 500 → now 200 + daemon_id). Full suite 377 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BAT-016's initial implementation passed only against mocked repositories and
broke on a real database. Two defects fixed:
1. insert_ledger_trade used `ON CONFLICT (portfolio_id, order_id, execution_id)
WHERE order_id IS NOT NULL` but portfolio_ledger is a TimescaleDB hypertable,
which cannot have that partial unique index (must include the partitioning
column recorded_at). The statement errored against Postgres. Replaced with
application-level idempotency: SELECT-before-insert inside the existing
`SELECT ... FOR UPDATE` portfolio row lock, which serializes concurrent
same-portfolio writes (race-safe). Duplicate returns current authoritative
portfolio state (dcy-006 OQ-4 b).
2. `with self._connect() as conn:` closes the shared psycopg3 connection on exit,
leaving the reused repository connection unusable. Switched to
`with conn.transaction():` (commits without closing).
Added tests/test_ledger_consolidation_integration.py — real-Postgres integration
tests (new / duplicate / negative-balance) that close the mock-only verification
gap. Full suite: 376 passed.
SDLC: REQ-036/037 CHG-20260605-03 reconcile OQ-7 (ii) (DB partial unique →
application-level idempotency) since the hypertable makes the DB constraint
infeasible. REQ-036/037/038 → Implemented; BAT-016 → release-candidate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- REQ-002 scope redefinition: portfolios.tag_ids → assets.tag_ids UUID[] column with GIN index
- REQ-010 augmentation: GET /v1/assets redesigned as admin catalog endpoint (admin-only, no token_type branching)
- Asset tagging infrastructure: tag_ids column, archived flag synthesis for inactive tags, list_assets N+1 optimization
- DB migration 20260521_0023: assets.tag_ids UUID[] NOT NULL DEFAULT '{}', idx_assets_tag_ids_gin
- Test suite: 17 new test cases for assets.tag_ids + GET /v1/assets AC-9~12 (362 tests total)
- Documentation: runtime flows, release baseline, batch verification all updated
- BAT-012 added to REL-001 Included Batch and marked release-candidate
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BAT-004 (REQ-004, REQ-005):
- app/api/v1_tags.py: POST /v1/tags, GET /v1/tags, PATCH /v1/tags/{id}/deactivate
- app/api/v1_portfolios.py: POST/DELETE /v1/portfolios/{id}/tags
- app/api/v1_assets.py: POST/DELETE /v1/assets/{id}/tags 엔드포인트 추가
- app/schemas/v1.py: TagItem, TagCreateRequest, TagListResponse, TagApplyRequest/Response 추가
- app/repositories/portfolio_repository.py, postgres.py: append/remove_portfolio_tag_id 추가
- app/main.py: v1_portfolios_router, v1_tags_router 등록
- tests/test_tags_http_api.py: 22개 HTTP API 테스트
BAT-005 (REQ-007, REQ-008):
- app/cli/commands/tag.py: pfhub tag create/list/deactivate/apply/remove 7개 커맨드 신규
- app/cli/main.py: tag_app 등록
- app/cli/commands/portfolio.py: pfhub portfolio tag add/list/remove 완전 제거(breaking change)
- app/cli/core/utils.py: print_tag_table + list[Tag] render 분기 추가
- tests/test_tags_cli.py: REQ-007 AC-1~9 커버 24개 CLI 테스트 신규
- tests/test_cli.py: 제거된 portfolio tag 테스트 2개 삭제
- docs/data-model.md, interface-contract.md, project-structure.md, runtime-flows.md: 태그 모델 baseline 갱신(REQ-008)
- docs/batches/: BAT-004, BAT-005 전체 산출물(release-candidate)
247 passed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- add get_portfolio_by_name_and_user to PortfolioRepository protocol and
PostgresPortfolioRepository; resolves AttributeError on all CLI name+user
resolution paths (REQ-017)
- add insert_ledger_trade to PortfolioRepository protocol and
PostgresPortfolioRepository; resolves HTTP 500 on POST
/v1/assets/{asset_id}/ledger/trades (REQ-008)
- add _row_to_ledger_trade static helper; uses INSERT ... ON CONFLICT DO
NOTHING RETURNING to return (LedgerTrade, created: bool)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add --daemon-id option to portfolio update command for daemon assignment
- Update managed_by_daemon_id field in portfolio updates
- Refactor print_portfolio_table() to print_portfolio_list() for better readability
- Display daemon_id in portfolio list output when assigned
- Show portfolio tags and managed daemon information in CLI list view
- Add daemon_id support to portfolio repository and service layers
- Update schemas to include managed_by_daemon_id field in responses
- Add BrokerCredential to domain models and repositories
- Implement upsert_broker_credential() for credential storage and rotation
- Implement list_broker_credentials() for credential retrieval
- Implement delete_broker_credential() for credential removal
- Add broker_credentials table schema with (user_id, broker) composite key
- Implement credential version tracking and last_rotated_at timestamps
- Add FakeRepo support for broker credential CRUD operations
- Add comprehensive test coverage for broker credential lifecycle
- Add daemon status tracking with state and metrics (idle, running, error)
- Implement pfhub daemon list CLI command for viewing registered daemons
- Extend auth schema and service for daemon lifecycle management
- Add postgres repository queries for daemon state persistence
- Expand test coverage for daemon registry and status transitions
- Update rel-006 release document with daemon model verification results
- Support daemon graceful shutdown and error recovery patterns
- Generate runtime_id for service-token issuance (CLI and API)
- Implement GET /v1/assets endpoint with asset view and strategy_params
- Implement GET /v1/user_info endpoint with trading_enabled and broker credentials
- Implement POST /v1/assets/{asset_id}/ledger/trades with idempotency support
- Update auth service to support runtime_id tracking and credential encryption
- Add comprehensive test coverage for v1 endpoints (dcy-002 implementation)
- Update postgres repository for ledger and broker credential queries
- Ensure all tests pass for feature validation
- Remove runtime_id from AuthToken model
- Remove market field from Portfolio model
- Remove BrokerCredential and LedgerTrade unused models
- Remove unique constraint on (owner_user_id, name)
- Simplify postgres repository schema and queries
- Clean up auth_service implementation
- Update test suite for simplified model
- Add service token rotation CLI command with force flag support
- Implement token rotation in auth service with expiration handling
- Add portfolio repository helper for transaction/batch operations
- Add cascade delete support in postgres repository
- Add comprehensive CLI tests for service token operations
- Add REQ-012 specification for service token rotation
- Create BAT-002 delivery batch for token rotation implementation
- Update SRS and batch indexes
- Add V1 API routes for assets, ledger, and user info with complete CRUD operations
- Implement authentication scopes and request validation via auth_routes.py
- Extend schemas with V1-specific models for accounts, holdings, and positions
- Add database queries and aggregations for P&L, performance, and account data
- Include comprehensive test coverage for all V1 endpoints and integration flows
- Update batch and release documentation with implementation and verification evidence
- Sync project structure and runtime flows per V1 API contract requirements
Closes implementation phase of BAT-001 (TrEx Basic V1 API Contract)