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:
2026-05-13 23:07:24 +00:00
parent f0a6caa178
commit e6b6a8715b
45 changed files with 2688 additions and 167 deletions
+23 -12
View File
@@ -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.