feat: implement daemon model with registry and single-asset assignment (BAT-010, REQ-023/028)
Core Implementation (BAT-010):
- Add daemon registry schema and CRUD operations (req-023)
- Implement single-daemon asset assignment constraint (one asset per daemon)
- Add daemon_id field to service_token for daemon identification
- Add daemon.py CLI module for daemon management commands
- Add 20260513_0016_daemon_model.py migration for daemon tables
API Changes:
- GET /v1/assets: include daemon_id in AssetResponse
- POST /v1/assets/{asset_id}/ledger/trades: require daemon token binding
- GET /v1/user_info: include daemon metadata
- Enhanced service-token rotate to preserve daemon bindings
CLI Commands:
- pfhub daemon register --name <name> --runtime-id <runtime-id>
- pfhub daemon list
- pfhub daemon get <daemon-id>
- pfhub daemon revoke <daemon-id>
Requirements (REQ-023, REQ-028):
- REQ-023: Daemon Registry and Service Token Daemon Binding (Interface, Approved)
* AC-1: daemon CRUD operations
* AC-2: service token daemon binding for POST /v1/assets/{asset_id}/ledger/trades
* AC-3: single daemon per asset (exact-one assignment model)
- REQ-028: README.md Daemon Provisioning Runbook update (Documentation, Approved)
Documentation:
- Add dcy-006 Discovery for daemon-id and single-daemon asset assignment
- Update README.md with Daemon Provisioning Runbook section
- Add BAT-010 batch documentation with planning, implementation, verification
- Update rel-006 release document (daemon model implementation)
- Update data-model.md with daemon and daemon_asset_assignment tables
- Update interface-contract.md with daemon registry APIs
- Update runtime-flows.md with daemon lifecycle flows
Breaking Changes:
- REQ-005~009, REQ-011~012 status reverted to Approved due to revised contract
* daemon_id field addition and exact-one asset assignment requirement
* Existing Implemented evidence invalidated by contract revision
* BAT-001/002/008 re-verification required
Testing:
- Add test_daemon_registry.py for daemon CRUD operations
- Add test_daemon_assignment.py for single-asset assignment constraints
- Update test_auth.py, test_cli.py, test_v1.py for daemon support
- Verify daemon token binding in trade endpoint authorization
Impact:
- Database: 2 new tables (daemons, daemon_asset_assignments)
- Migration: 20260513_0016 creates daemon model schema
- CLI: 4 new daemon subcommands
- API: daemon_id propagation through v1 endpoints
- Verification: BAT-001/002/008 re-verification gates next release
This commit is contained in:
+23
-12
@@ -4,8 +4,8 @@
|
||||
- Baseline Seed: .stagepilot/bootstrap/baseline.yaml
|
||||
- Status: confirmed
|
||||
- Owner: repository-maintainers
|
||||
- Last Updated (KST): 2026-05-11 00:00
|
||||
- Source Discovery / Batch: bootstrap-baseline, bat-001, bat-006, bat-008
|
||||
- Last Updated (KST): 2026-05-13 00:00
|
||||
- Source Discovery / Batch: bootstrap-baseline, bat-001, bat-006, bat-008, bat-010
|
||||
- Primary Runtime: mixed
|
||||
|
||||
## Project Summary
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
### Inputs
|
||||
|
||||
- pfhub subcommands :: include user, portfolio, strategy, service-token, health-check, init-db, and migrate-db command families
|
||||
- pfhub subcommands :: include user, portfolio, strategy, service-token, daemon, credential, health-check, init-db, and migrate-db command families
|
||||
- Flags and options :: include --format table|json, --force confirmations, and operation-specific mutation arguments
|
||||
- Environment configuration :: app/core/config.py resolves PORTFOLIOHUB_* and TIMESCALEDB_* values before command execution
|
||||
- Portfolio identification (bat-006) :: all portfolio mutation commands accept `<name>` (positional) + `--user <username>` instead of raw portfolio_id; `comment` and `tag` sub-commands still accept portfolio_id (UUID) directly
|
||||
@@ -95,8 +95,8 @@
|
||||
## Interface: `v1 Consumer API (trex-basic)`
|
||||
|
||||
- Type: http-api
|
||||
- Consumers / Producers: trex-basic service (Consumer-Driven Contract, dcy-002)
|
||||
- Purpose: provide asset listing, user info, and trade ledger recording for the trex-basic trading daemon
|
||||
- Consumers / Producers: trex-basic service (Consumer-Driven Contract, dcy-002, dcy-006)
|
||||
- Purpose: provide daemon-scoped asset listing, user info with daemon_id diagnostic, and daemon-assignment-authorized trade ledger recording
|
||||
- Stability: internal (versioned under /v1)
|
||||
|
||||
### Authentication
|
||||
@@ -105,10 +105,18 @@
|
||||
- Token must be a DB-issued service token (`token_source="db"`, `token_type="service"`); env tokens are rejected with HTTP 403.
|
||||
- POST /v1/assets/{asset_id}/ledger/trades additionally requires `trade-write` permission.
|
||||
|
||||
### Daemon Authorization Model
|
||||
|
||||
- Service tokens are bound to a daemon at issuance via `daemon_id` field. Legacy/unbound tokens have `daemon_id=NULL`.
|
||||
- GET /v1/assets and POST /v1/assets/{asset_id}/ledger/trades are daemon-scoped. NULL token `daemon_id` → HTTP 403 (fail-closed).
|
||||
- GET /v1/user_info is available to any service token; returns `daemon_id: null` as diagnostic for unbound tokens.
|
||||
- Each asset (portfolio) may be assigned to exactly one daemon via `portfolios.managed_by_daemon_id`. Mismatch or unassigned asset → HTTP 403 on ledger writes.
|
||||
|
||||
### Endpoint: `GET /v1/assets`
|
||||
|
||||
- Auth: service-token bearer (read)
|
||||
- Auth: service-token bearer (read); token must have `daemon_id` bound (NULL → HTTP 403)
|
||||
- Query Params: `?status=<value>` (optional, filters by portfolio status)
|
||||
- Filter: returns only assets where `portfolios.managed_by_daemon_id = token.daemon_id`
|
||||
- Response: HTTP 200
|
||||
|
||||
```
|
||||
@@ -139,16 +147,17 @@
|
||||
}
|
||||
```
|
||||
|
||||
- Error: HTTP 401 if no token; HTTP 403 if not a service token
|
||||
- Error: HTTP 401 if no token; HTTP 403 if not a service token or daemon_id is NULL
|
||||
|
||||
### Endpoint: `GET /v1/user_info`
|
||||
|
||||
- Auth: service-token bearer (read); token must have `runtime_id` assigned
|
||||
- Auth: service-token bearer (read); available to all service tokens including unbound (daemon_id=NULL)
|
||||
- Response: HTTP 200
|
||||
|
||||
```
|
||||
{
|
||||
"runtime_id": "<str>",
|
||||
"runtime_id": "<str|null>", // null for tokens without runtime_id (diagnostic)
|
||||
"daemon_id": "<str|null>", // null for unbound/legacy tokens (diagnostic)
|
||||
"trading_enabled": <bool>, // mapped from users.is_active
|
||||
"disabled_reason": "<str|null>", // "account_inactive" when trading_enabled=false
|
||||
"brokers": [
|
||||
@@ -167,12 +176,13 @@
|
||||
}
|
||||
```
|
||||
|
||||
- Error: HTTP 401 if no token; HTTP 403 if not a service token; HTTP 404 if token has no runtime_id
|
||||
- Error: HTTP 401 if no token; HTTP 403 if not a service token
|
||||
|
||||
### Endpoint: `POST /v1/assets/{asset_id}/ledger/trades`
|
||||
|
||||
- Auth: service-token bearer (trade-write)
|
||||
- Auth: service-token bearer (trade-write); token must have `daemon_id` bound; asset must be assigned to that daemon
|
||||
- Path Param: `asset_id` — must be a valid UUID portfolio_id
|
||||
- Authorization: `portfolios.managed_by_daemon_id` must equal `token.daemon_id`; NULL token → 403; unassigned asset → 403; mismatch → 403
|
||||
- Request Body:
|
||||
|
||||
```
|
||||
@@ -208,7 +218,7 @@
|
||||
```
|
||||
|
||||
- Response HTTP 200 (idempotent duplicate with identical payload): same body with `"status": "duplicate"`
|
||||
- Error: HTTP 401 if no token; HTTP 403 if not service token or insufficient permission; HTTP 404 if asset_id not found; HTTP 409 if duplicate key with different payload
|
||||
- Error: HTTP 401 if no token; HTTP 403 if not service token, daemon_id NULL, asset unassigned, or daemon mismatch; HTTP 404 if asset_id not found; HTTP 409 if duplicate key with different payload
|
||||
|
||||
### Idempotency Contract
|
||||
|
||||
@@ -242,6 +252,7 @@
|
||||
|
||||
## Change Log
|
||||
|
||||
- 2026-05-13 KST: bat-010 — REQ-006/007/008/011/023: added daemon authorization model section; updated GET /v1/assets (daemon-scoped filter, NULL 403); updated GET /v1/user_info (daemon_id field, NULL runtime_id → 200+null diagnostic); updated POST /v1/assets/{asset_id}/ledger/trades (daemon assignment authorization, fail-closed policy); added daemon CLI subcommand to CLI surface; updated v1 purpose and source discovery.
|
||||
- 2026-05-11 KST: bat-008 — REQ-011 AC-1: expanded v1 Consumer API section with authentication contract, detailed request/response schemas for GET /v1/assets, GET /v1/user_info, POST /v1/assets/{asset_id}/ledger/trades; updated GET /health response schema (REQ-004); added idempotency contract.
|
||||
- 2026-05-11 KST: bat-006 — Added v1 consumer API interface section (trex-basic); noted asset_id UUID change and simultaneous deployment constraint; updated CLI portfolio inputs to reflect name+user identification.
|
||||
- 2026-04-30 KST: Bootstrap baseline created from repository observation for initial StagePilot adoption.
|
||||
Reference in New Issue
Block a user