genichin 21f268ac53
Verify container release contracts / verify (push) Canceled after 0s
Publish verified PortfolioHub image / verify (push) Canceled after 0s
Publish verified PortfolioHub image / publish (push) Canceled after 0s
chore(release): v0.4.0
2026-09-19 21:32:21 +09:00
2026-09-19 21:32:21 +09:00
2026-09-19 21:32:21 +09:00
2026-09-19 21:32:21 +09:00
2026-09-19 21:32:21 +09:00
2026-09-19 21:32:21 +09:00

Portfolio Hub

Portfolio Hub is a FastAPI service that extracts portfolio management from jsbot-kis and exposes it through an authenticated HTTP API.

Current status

  • FastAPI application entrypoint is available.
  • Token-based authentication and role checks are implemented.
  • Portfolio and user management API contracts are implemented.
  • Core portfolio domain services are ported from jsbot-kis.
  • Postgres-backed portfolio and user persistence is implemented and gated behind pfhub_DB_ENABLED=true.
  • Alembic-managed schema migrations are applied in production (head: 20260605_0025).
  • Trade history and realized PnL query API is implemented (GET /v1/portfolios/{id}/trades, GET /v1/portfolios/{id}/pnl/realized).

Configuration

Copy .env.example to .env and edit values as needed:

cp .env.example .env

The application loads configuration in this order (higher priority first):

  1. Environment variables
  2. Variables from .env file
  3. Default values in code

When deploying to production, set environment variables directly instead of using .env.

Key configuration options

  • DB repository switch: pfhub_DB_ENABLED (enables DB-backed persistence)
  • Initial user bootstrap: pfhub_BOOTSTRAP_USERNAME, pfhub_BOOTSTRAP_EMAIL
  • Initial user password: pfhub_BOOTSTRAP_PASSWORD
  • Session/token TTL: pfhub_SESSION_TOKEN_TTL_HOURS, pfhub_CLIENT_TOKEN_DEFAULT_TTL_DAYS, pfhub_SERVICE_TOKEN_DEFAULT_TTL_DAYS
  • TimescaleDB / PostgreSQL: TIMESCALEDB_* (single source of DB connection settings)
  • Redis: REDIS_* (for caching and sessions)
  • Loki: LOKI_* (for centralized logging)
  • Authentication: pfhub_*_TOKENS (bearer tokens with role-based access)

For complete configuration options, see .env.example.

Run locally

python -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[dev]'

# Configure .env
cp .env.example .env

# Start server
uvicorn app.main:app --reload

# In another terminal, run tests
pytest

If you need local infrastructure for TimescaleDB, Redis, and Loki, start them with Docker Compose before running the app:

docker compose -f docker-compose.local-test.yml up -d
docker compose -f docker-compose.local-test.yml ps

Default endpoints exposed by docker-compose.local-test.yml:

  • TimescaleDB: 127.0.0.1:5432
  • Redis: 127.0.0.1:6379 (RedisInsight UI: 127.0.0.1:8001)
  • Loki: 127.0.0.1:3100

Typical .env values for this local setup:

pfhub_DB_ENABLED=true
TIMESCALEDB_HOST=127.0.0.1
TIMESCALEDB_PORT=5432
TIMESCALEDB_USER=portfolio_hub_user
TIMESCALEDB_PASSWORD=change-me
TIMESCALEDB_NAME=portfolio_hub

REDIS_HOST=127.0.0.1
REDIS_PORT=6379

LOKI_HOST=127.0.0.1
LOKI_PORT=3100

To stop and remove the local containers:

docker compose -f docker-compose.local-test.yml down

CLI

The repository now includes a pfhub CLI that mirrors the main portfolio flows from jsbot-kis. The same commands are also available through the shorter alias phub.

pfhub --help
pfhub user list
pfhub user create --username alice --password password123 --email alice@example.com --force
pfhub user get <username>
pfhub user update <username> --username alice-updated --force
pfhub user delete <username> --force
pfhub service-token list --user alice
pfhub service-token issue nightly --user alice --ttl-days 30
pfhub service-token issue permanent --user alice --no-expiry
pfhub service-token issue my-daemon-token --user alice --daemon-id <daemon_id>
pfhub service-token rename nightly --new-name nightly-kis
pfhub service-token revoke nightly --force
pfhub service-token rotate permanent --force
pfhub daemon create alpha-bot --user alice --display-name "Alpha Trading Bot"
pfhub daemon list --user alice
pfhub daemon update <daemon_id> --display-name "Updated Name"
pfhub daemon update <daemon_id> --deactivate
pfhub portfolio list --user <username>
pfhub portfolio status 005930:tv --recent 10
pfhub portfolio create 005930:tv --symbol 005930 --user <username> --cash-balance 10000000 --force
pfhub portfolio buy 005930:tv --quantity 10 --price 71000 --fee 5000 --force
pfhub portfolio sell 005930:tv --quantity 5 --price 72000 --tax 3000 --force
pfhub portfolio invest 005930:tv --type deposit --amount 50000 --force
pfhub portfolio transfer 005930:tv --direction to-buffer --amount 100000 --force
pfhub portfolio reset 005930:tv --force
pfhub portfolio delete 005930:tv --keep-ledger --force

Supported commands:

  • init-db
  • migrate-db
  • user list
  • user get
  • user create
  • user update
  • user delete
  • service-token list
  • service-token issue
  • service-token rename
  • service-token revoke
  • service-token rotate
  • daemon create
  • daemon list
  • daemon update
  • portfolio list
  • portfolio status
  • portfolio create
  • portfolio update
  • portfolio buy
  • portfolio sell
  • portfolio invest
  • portfolio transfer
  • portfolio reset
  • portfolio delete
  • health-check

Write commands ask for confirmation by default. Use --force to skip the prompt for scripts and automation.

Service token issue policy:

  • 기본값은 365일 TTL이다.
  • --ttl-days와 --no-expiry는 동시에 사용할 수 없다.
  • --no-expiry는 관리자 권한에서만 허용된다.
  • 토큰 원문은 발급 응답 1회 이후 마스킹된 값으로만 출력/기록된다.

runtime_id vs daemon_id

Two stable identifiers are attached to service tokens:

  • runtime_id: per-token opaque ID, regenerated on every service-token rotate. Use to track which token generation is currently active (GET /v1/user_info returns it for live verification).
  • daemon_id: stable logical identity for a trading daemon process. Bound at token issuance and preserved across rotates (rotate invariant). Use to scope daemon-owned assets and authorize ledger writes.

A token with daemon_id=null is a legacy/unbound token. GET /v1/assets and POST /v1/assets/{asset_id}/ledger/trades return HTTP 403 for null-daemon tokens (fail-closed). GET /v1/user_info returns 200 with daemon_id: null as a diagnostic (not an error).

The CLI uses the same repository and settings layer as the HTTP API. To target the external TimescaleDB, set pfhub_DB_ENABLED=true and provide TIMESCALEDB_* values in the environment or .env.

Initialize a new database explicitly before the first use:

pfhub_DB_ENABLED=true pfhub init-db
pfhub_DB_ENABLED=true pfhub health-check
pfhub_DB_ENABLED=true pfhub portfolio list

init-db will:

  • connect to the maintenance database, defaulting to postgres
  • create the target database if it does not exist
  • run Alembic upgrade head

migrate-db will:

  • connect to the target database
  • run Alembic upgrade head
  • apply any future schema revisions in order

This repository now uses Alembic for versioned schema migrations. Future schema changes should be added under migrations/versions.

Daemon Provisioning Runbook

이 섹션은 daemon registry 등록, service-token 바인딩, asset 할당, 그리고 daemon-scoped enforcement 활성화 절차를 정의한다.

1. Daemon 등록

# 1. daemon 생성 (daemon_slug는 per-user unique)
pfhub_DB_ENABLED=true pfhub daemon create alpha-bot --user alice --display-name "Alpha Trading Bot"
# 출력: daemon_id (UUID), daemon_slug, is_active=True

# 2. daemon 목록 확인
pfhub_DB_ENABLED=true pfhub daemon list --user alice

2. Service Token을 Daemon에 바인딩

# daemon_id는 위 create 출력에서 확인
pfhub_DB_ENABLED=true pfhub service-token issue alpha-bot-token --user alice \
  --daemon-id <daemon_id> --permission trade-write --no-expiry

# 발급된 토큰 원문은 1회만 표시됨. daemon_id와 runtime_id를 기록해 둘 것.

바인딩 규칙:

  • daemon은 존재하고 is_active=True여야 한다.
  • daemon의 owner_user_id가 토큰 발급 대상 user와 일치해야 한다.
  • 비활성 daemon에 대한 token 발급은 즉시 거부된다.

3. Asset을 Daemon에 할당

현재 managed_by_daemon_id 할당은 DB 직접 수정 또는 배포 시 마이그레이션으로 수행한다(CLI portfolio update를 통한 daemon 할당은 향후 추가 예정).

UPDATE portfolios SET managed_by_daemon_id = '<daemon_id>' WHERE name = 'my-portfolio' AND owner_user_id = '<user_id>';

4. Daemon-Scoped Enforcement 활성화 게이트

daemon-scoped enforcement(NULL token → 403)는 코드 레벨에서 이미 활성화되어 있다. 프로덕션 전환 전 아래 4가지 증거를 확보해야 한다:

# 항목 충족 기준
1 Active NULL daemon_id token inventory 완료 SELECT COUNT(*) FROM auth_tokens WHERE daemon_id IS NULL AND token_type='service' AND revoked_at IS NULL = 0 (또는 의도된 레거시 토큰만 남음)
2 Dry-run 결과 zero active-NULL 대상 환경에서 위 쿼리 실행, 결과 스크린샷 또는 텍스트 캡처
3 Dry-run 결과 zero mismatch SELECT COUNT(*) FROM portfolios p JOIN auth_tokens t ON p.managed_by_daemon_id != t.daemon_id WHERE t.token_type='service' AND t.revoked_at IS NULL = 0
4 Release approver 승인 위 증거 3가지를 release 문서에 첨부하고 release approver 서명 확보

마이그레이션 20260513_0016이 적용된 환경에서만 enforcement가 정상 동작한다.

5. Service Token Rotate (daemon_id invariant)

pfhub_DB_ENABLED=true pfhub service-token rotate alpha-bot-token --force
# runtime_id는 새로 발급됨; daemon_id는 유지됨

rotate 후 GET /v1/user_info로 새 runtime_id와 기존 daemon_id를 교차 확인한다:

curl -X GET "<BASE_URL>/v1/user_info" \
  -H "Authorization: Bearer <NEW_TOKEN>"
# 응답: runtime_id=<new_id>, daemon_id=<same_daemon_id>

Local DB Validation Runbook

이 섹션은 로컬 docker compose 환경에서 DB 초기화, 마이그레이션, health-check, smoke flow를 반복 검증하기 위한 절차를 정의한다.

준비 — 컨테이너 시작

docker compose -f docker-compose.local-test.yml up -d
docker compose -f docker-compose.local-test.yml ps
# portfolio-hub-timescaledb 상태가 (healthy) 인지 확인

기본 접속 정보 (.env 설정 기준):

  • TimescaleDB: 127.0.0.1:5432
  • DB: portfolio_hub, User: portfolio_hub_user

Scenario A — Fresh DB (DB 미존재 상태)

# 1. DB 생성 + Alembic upgrade head
pfhub_DB_ENABLED=true pfhub init-db
# 기대: database: portfolio_hub / created: True / schema_initialized: True / migration_applied: head

# 2. DB 연결 health 확인
pfhub_DB_ENABLED=true pfhub health-check
# 기대: db_enabled: True / repository: PostgresPortfolioRepository

# 3. Smoke flow — 사용자 생성 + portfolio 생성 + 목록 확인
pfhub_DB_ENABLED=true pfhub user create --username alice --password password123 --force
pfhub_DB_ENABLED=true pfhub portfolio create my-portfolio --symbol 005930 --user alice --cash-balance 1000000 --force
pfhub_DB_ENABLED=true pfhub portfolio list --user alice
# 기대: my-portfolio가 목록에 포함

pass 기준: 각 명령 exit code 0 / 기대 필드 출력 확인
fail 기준: connection refused / Alembic revision 오류 / exit code ≠ 0

Scenario B — runtime-precreated schema (App 선행 기동)

# 1. App 먼저 기동 (ensure_schema() 실행)
pfhub_DB_ENABLED=true uvicorn app.main:app &

# 2. migrate-db로 Alembic 관리 상태로 전환
pfhub_DB_ENABLED=true pfhub migrate-db
# 기대: migration_applied: head

# 3. health-check 재확인
pfhub_DB_ENABLED=true pfhub health-check

동작 원리: bootstrap_or_upgrade_to_head()가 alembic_version 테이블 없음 → 기존 핵심 테이블 존재 → legacy stamp → head upgrade 순서로 실행한다.

반복 실행 (재현성 확인)

# 컨테이너 + 볼륨 초기화
docker compose -f docker-compose.local-test.yml down -v

# 위 Scenario A 또는 B 재실행

출력이 이전 실행과 동일한지 비교한다. JSON 출력 수집:

pfhub_DB_ENABLED=true pfhub init-db --output json
pfhub_DB_ENABLED=true pfhub health-check --output json

로그 수집 위치: docker compose -f docker-compose.local-test.yml logs portfolio-hub-timescaledb

Rollback / 재시도 판단 기준

증상 판단 조치
init-db: connection refused DB 컨테이너 미준비 docker compose ps → (healthy) 대기 후 재시도
migrate-db: Alembic revision 오류 revision 파일 손상 또는 순서 오류 migrations/versions/ revision 확인
migrate-db 후 v1 API 오류 ensure_schema() 선행 후 Alembic 미실행 migrate-db 재실행
데이터 오염으로 스키마 불일치 심각한 데이터 오류 볼륨 초기화(down -v) + Scenario A 재시작

Release Gate Criteria

다음 조건을 모두 만족해야 로컬 검증 완료로 간주한다:

  1. Scenario A: init-db exit code 0 + health-check exit code 0 + smoke flow 정상 완료
  2. migrate-db 반복 실행 시 오류 없음 (멱등성 확인)
  3. docker compose logs에 ERROR 레벨 로그 없음

Docs Sync Guardrail

To keep implementation and specification documents in sync, this repository provides an automated check.

Rules enforced:

  • If files under app/ change, at least one docs/technical/ document must be updated in the same commit/PR.
  • If files under app/api/ or app/schemas/ change, docs/technical/api-contracts.md must be updated in the same commit/PR.
  • If files under migrations/versions/ change, docs/technical/persistence-and-migrations.md must be updated in the same commit/PR.
  • Changed migration revision IDs must appear in docs/technical/persistence-and-migrations.md.

Enable local pre-commit check:

pip install -e .[dev]
pre-commit install

Run manually:

python3 scripts/check_docs_sync.py --staged

CI check:

  • GitHub Actions workflow: .github/workflows/docs-sync-check.yml
  • Runs automatically on pull requests.

Authentication model

pfhub currently supports bearer tokens with three permission levels.

  • read: list and status endpoints
  • trade-write: mutation endpoints such as buy, sell, invest, transfer, reset
  • admin: delete, user management, and future operational actions

User management

User management is exposed through both HTTP API and CLI.

  • API base route: /users
  • Permission: admin only
  • Supported operations: list, get, create, update, delete

Frontend and app authentication is exposed through /auth.

  • POST /auth/login: username/password login, returns bearer session token
  • GET /auth/tokens: list current user's issued tokens
  • POST /auth/tokens: issue a client token for app/script usage
  • DELETE /auth/tokens/{token_id}: revoke a previously issued token

Service tokens (long-lived integration tokens) are managed via CLI:

pfhub service-token issue <name> --user <username> [--permission read|trade-write] [--ttl-days N | --no-expiry]
pfhub service-token revoke <name> [--force]

Example API calls:

curl -X GET "http://localhost:8000/users" \
	-H "Authorization: Bearer <ADMIN_TOKEN>"

curl -X POST "http://localhost:8000/users" \
	-H "Authorization: Bearer <ADMIN_TOKEN>" \
	-H "Content-Type: application/json" \
	-d '{"username": "alice", "email": "alice@example.com"}'

The default .env.example includes example tokens for development.

When pfhub_DB_ENABLED=true and both pfhub_BOOTSTRAP_USERNAME and pfhub_BOOTSTRAP_PASSWORD are set, the app creates that admin user automatically on startup only if the users table is empty. This is intended for first-run bootstrap, not recurring synchronization.

Portfolio API

Portfolio management and ledger access are exposed through /v1/portfolios.

  • POST /v1/portfolios: create a portfolio (requires trade-write or session token)
  • GET /v1/portfolios: list portfolios for the authenticated user
  • PATCH /v1/portfolios/{id}: update portfolio metadata
  • DELETE /v1/portfolios/{id}: delete a portfolio (requires admin)
  • POST /v1/portfolios/{id}/ledger/trades: record a trade entry with accounting (cash_balance_after, asset_quantity_after)
  • GET /v1/portfolios/{id}/tags: list tags on a portfolio
  • POST /v1/portfolios/{id}/tags: apply a tag to a portfolio
  • DELETE /v1/portfolios/{id}/tags/{tag_id}: remove a tag from a portfolio
  • POST /v1/portfolios/{id}/comments: add a comment
  • GET /v1/portfolios/{id}/comments: list comments
  • PATCH /v1/portfolios/{id}/comments/{comment_id}: update a comment
  • DELETE /v1/portfolios/{id}/comments/{comment_id}: delete a comment
  • GET /v1/portfolios/{id}/trades: query trade history with date filter (requires read or higher)
  • GET /v1/portfolios/{id}/pnl/realized: compute WAC-based realized PnL over a date range (requires read or higher)

Trade history query (GET /v1/portfolios/{id}/trades)

Supports single-date and range queries via query parameters. At least one date parameter is required.

# Single date
curl -X GET "http://localhost:8000/v1/portfolios/<id>/trades?date=2026-06-19" \
    -H "Authorization: Bearer <READ_TOKEN>"

# Date range
curl -X GET "http://localhost:8000/v1/portfolios/<id>/trades?from=2026-06-01&to=2026-06-19" \
    -H "Authorization: Bearer <READ_TOKEN>"

Response fields per trade item: trade_id, side, quantity, price, executed_at, fee_amount, tax_amount.

Realized PnL query (GET /v1/portfolios/{id}/pnl/realized)

Computes weighted-average cost (WAC) based realized PnL over the specified date range. from and to are required. symbol is an optional filter.

curl -X GET "http://localhost:8000/v1/portfolios/<id>/pnl/realized?from=2026-06-01&to=2026-06-19" \
    -H "Authorization: Bearer <READ_TOKEN>"

Response fields: total_realized_pnl, cost_basis_from, items (per-sell realized PnL), data_completeness_warning (set when buy history before from is incomplete).

Production Operator Runbook

이 섹션은 실배포 환경에서 admin 접근, release smoke credential 관리, provenance 확인, 로그 증적 수집을 반복 가능한 절차로 정리한다.

1. Canonical admin access path

  • steady-state 운영 경로는 운영자 개인 소유 admin 사용자 계정으로 로그인해 얻은 session token 또는 그 컨텍스트에서 발급한 client token이다.
  • POST /auth/login으로 admin auth context를 확보한 뒤, 같은 컨텍스트로 POST /auth/service-tokens를 호출해 smoke용 service-token을 발급한다.
  • bootstrap 환경 변수 기반 admin 생성은 users 테이블이 비어 있는 신규 환경 또는 disaster recovery 직후 admin 사용자 부재가 확인된 경우에만 허용한다.
  • 환경 변수 기반 admin token은 /opt/portfoliohub/.env에서만 꺼내는 break-glass fallback으로만 사용하고, 정기 release smoke의 표준 경로로 사용하지 않는다.
  • 현재 프로덕션 secret material location은 portfoliohub systemd service의 EnvironmentFile=/opt/portfoliohub/.env 이다. 로컬 워크스페이스 파일이나 release 문서는 canonical storage가 아니다.

2. Smoke credential ownership policy

역할 책임:

  • deployment secret custodian (repository-maintainers 역할 내): secret material 보관 위치(/opt/portfoliohub/.env) 및 접근 통제 관리 책임자.
  • production operator / release approver: 실배포 smoke 수행 및 release 승인 책임자. read/trade-write smoke service-token 공동 소유.

정책:

  • admin 로그인 자격증명은 named operator가 개인별로 소유하고 공유하지 않는다.
  • read/trade-write smoke service-token은 개인 소유가 아니라 환경 단위 운영 credential로 취급하고, release approver 또는 repository-maintainers 역할이 공동 소유한다.
  • raw password, raw session token, raw client token, raw service-token은 현재 프로덕션 기준 /opt/portfoliohub/.env와 동일 접근 통제 하의 운영 저장소에만 저장한다. 관리 주체: deployment secret custodian.
  • release 문서와 운영 메모에는 token name, token_prefix, permission, runtime_id, issued_at, revoked_at만 남기고 raw secret은 남기지 않는다.
  • 토큰은 release smoke 전 발급 또는 rotate하고, 노출 의심, 담당자 변경, 장애 대응 후에는 revoke 또는 rotate한다.

3. Release credential matrix

Credential 용도 발급 주체 보관 위치 소유 형태 폐기 조건
public/no-auth GET /health, /docs, /openapi.json 없음 없음 해당 없음 해당 없음
read service-token GET /v1/assets, GET /v1/user_info admin context로 POST /auth/service-tokens /opt/portfoliohub/.env 관리 경로 환경 단위 공동 소유 (release approver / repository-maintainers) 노출 의심, 담당자 변경, 장애 후 revoke/rotate
trade-write service-token POST /v1/assets/{asset_id}/ledger/trades admin context로 POST /auth/service-tokens /opt/portfoliohub/.env 관리 경로 환경 단위 공동 소유 (release approver / repository-maintainers) 노출 의심, 담당자 변경, 장애 후 revoke/rotate
admin auth context POST /auth/service-tokens (token 발급) /auth/login (steady-state) 또는 break-glass env token 개인 소유 (공유 금지) named operator 개인 소유 담당자 퇴임, 노출 의심
  1. 운영자 개인 admin 계정으로 로그인해 admin session token을 확보한다.
  2. 필요하면 동일 컨텍스트로 client token을 발급해 짧은 작업 단위 credential로 전환한다.
  3. admin auth context로 POST /auth/service-tokens를 호출해 read smoke token과 trade-write smoke token을 분리 발급한다.
  4. raw token은 즉시 /opt/portfoliohub/.env 기준 운영 저장소에 저장하고, release evidence에는 token name, token_prefix, permission, runtime_id만 기록한다.
  5. public/no-auth, read service-token, trade-write service-token, admin auth context를 분리해 smoke를 수행한다.

예시:

curl -X POST "<BASE_URL>/auth/login" \
	-H "Content-Type: application/json" \
	-d '{"username":"<ADMIN_USERNAME>","password":"<ADMIN_PASSWORD>"}'

curl -X POST "<BASE_URL>/auth/service-tokens" \
	-H "Authorization: Bearer <ADMIN_SESSION_OR_CLIENT_TOKEN>" \
	-H "Content-Type: application/json" \
	-d '{"name":"prod-smoke-read","username":"<SMOKE_USERNAME>","permission":"read"}'

curl -X POST "<BASE_URL>/auth/service-tokens" \
	-H "Authorization: Bearer <ADMIN_SESSION_OR_CLIENT_TOKEN>" \
	-H "Content-Type: application/json" \
	-d '{"name":"prod-smoke-trade-write","username":"<SMOKE_USERNAME>","permission":"trade-write"}'

5. Provenance check procedure

  • pfhub service-token list --user <username>로 token name, token_prefix, token_id, user_id, permission, created_at, last_used_at, revoked_at를 확인한다.
  • pfhub service-token rotate <name> --force로 무기한 token을 rotate하고 새 runtime_id를 확보한다.
  • pfhub user get <username>로 사용자에 연결된 service token 이름 목록을 교차 확인한다.
  • GET /v1/user_info를 live service-token으로 호출해 runtime_id와 permission을 확인하고, 발급 또는 rotate 시 기록한 runtime_id와 대조한다.

6. Log and evidence collection

Canonical smoke environment: production을 canonical acceptance 환경으로 사용한다. staging smoke는 optional preflight로만 사용하며 release acceptance 기준이 되지 않는다. 절차의 source of truth는 이 README이며, 실행 evidence의 canonical record는 해당 release 문서에 남긴다.

로그 접근 경로:

  • 현재 production log stack은 Linux/Ubuntu host에서 systemd로 구동되는 portfoliohub uvicorn 서비스와 Caddy reverse proxy다.
  • startup 및 애플리케이션 로그는 sudo journalctl -u portfoliohub -n 200 --no-pager 또는 sudo journalctl -u portfoliohub -f로 확인한다.
  • reverse proxy 또는 access 수준 로그는 sudo journalctl -u caddy -n 200 --no-pager 또는 sudo journalctl -u caddy -f로 확인한다.
  • Loki는 LOKI_ENABLED와 실제 수집 파이프라인이 확인된 환경에서만 보조 집계 경로로 취급하며, 현재 canonical production path는 아니다.
  • 외부 URL만으로는 로그 접근이 되지 않으므로, 운영자는 production host의 systemd journal 조회 권한을 가져야 한다.

HTTP 오류 분류:

  • 401 Unauthorized: bearer token 미제공 또는 유효하지 않은 token. 인증 정보 자체가 없거나 만료된 경우.
  • 403 Forbidden: token은 유효하나 해당 엔드포인트에 필요한 permission 미보유. read token으로 trade-write 엔드포인트 호출 시 발생.
  • 404 Not Found: 인증/권한은 통과했으나 대상 리소스(portfolio, asset 등)가 DB에 존재하지 않음. 권한 문제와 혼동하지 않도록 주의.

Release evidence 기록 필드:

  • 포함: 시각(executed_at, completed_at), endpoint, HTTP status code, source system(journalctl unit name), token name, token_prefix, runtime_id
  • 제외: raw secret, raw token 값

Planned next steps

  1. Add integration tests for DB-backed portfolio and user flows.
  2. Add unrealized PnL (current price integration) — deferred from dcy-007.
  3. Add pagination for trade history and PnL endpoints.
  4. Remove legacy POST /portfolios/{id}/buy and POST /portfolios/{id}/sell endpoints (currently deprecated-only).
  5. Fix pfhub tag apply asset / pfhub tag remove asset CLI mismatch (targets portfolios table instead of assets).
S
Description
No description provided
Readme
2.7 MiB
Languages
Python 99.9%
Dockerfile 0.1%