feat: 实现起卦、设置与积分系统
This commit is contained in:
@@ -13,6 +13,18 @@ This document is the source of truth for backend RFC7807 `code` values consumed
|
||||
| `AUTH_REFRESH_TOKEN_MISSING` | 401 | Refresh token missing on logout | Treat as local logout and clear session |
|
||||
| `AUTH_USER_NOT_FOUND` | 404 | User not found | Show not-found message where applicable |
|
||||
|
||||
## Agent Points
|
||||
|
||||
| code | status | meaning | frontend handling |
|
||||
|---|---:|---|---|
|
||||
| `POINTS_INSUFFICIENT_BALANCE` | 402 | Not enough points to start this run | Show recharge/insufficient-points prompt |
|
||||
|
||||
## Agent Session
|
||||
|
||||
| code | status | meaning | frontend handling |
|
||||
|---|---:|---|---|
|
||||
| `AGENT_SESSION_RUN_LIMIT_EXCEEDED` | 409 | Session already reached max run count (start + 3 follow-ups) | Show run-limit message and require starting a new session |
|
||||
|
||||
Compatibility strategy:
|
||||
|
||||
- Additive changes only for new codes.
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
# User Points & Chat Data Protocol
|
||||
|
||||
This protocol defines the canonical data contract for user profile, points account, points ledger, chat session, and chat messages.
|
||||
|
||||
Protocol verification status:
|
||||
|
||||
- Last audited migration: `backend/alembic/versions/20260403_0004_remove_points_reason_code.py`
|
||||
- Last audited models: `backend/src/models/profile.py`, `backend/src/models/user_points.py`, `backend/src/models/points_ledger.py`, `backend/src/models/agent_chat_session.py`, `backend/src/models/agent_chat_message.py`
|
||||
- Current status: aligned
|
||||
|
||||
## Scope
|
||||
|
||||
- `profiles`
|
||||
- `user_points`
|
||||
- `points_ledger`
|
||||
- `sessions`
|
||||
- `messages`
|
||||
|
||||
## Compatibility strategy
|
||||
|
||||
- Current strategy: additive evolution.
|
||||
- Breaking changes (drop/rename/type change on core fields) require explicit migration + rollback notes.
|
||||
- `points_ledger.metadata.schema_version` is mandatory and current value is `1`.
|
||||
|
||||
## Runtime charging policy (chat)
|
||||
|
||||
- Charge unit: `20` points per successful run.
|
||||
- Charge timing: deduct after worker run succeeds (`RUN_FINISHED` path).
|
||||
- Failure behavior: failed/canceled runs do not deduct points.
|
||||
- Precheck: before accepting a run, backend must verify `available = balance - frozen_balance >= 20`.
|
||||
- Session follow-up cap: one session allows at most 4 user runs total (initial divination + 3 follow-ups).
|
||||
- Billing idempotency key for per-run consume: `chat.run.success:{session_id}:{run_id}`.
|
||||
|
||||
## Table contract
|
||||
|
||||
### profiles
|
||||
|
||||
- PK: `id` (`auth.users.id`, `on delete cascade`)
|
||||
- Core fields: `username`, `avatar_url`, `bio`, `settings`, `created_at`, `updated_at`, `deleted_at`
|
||||
- Constraints:
|
||||
- `username` not empty
|
||||
- Indexes:
|
||||
- `ix_profiles_username`
|
||||
- `ix_profiles_settings_gin`
|
||||
|
||||
### user_points
|
||||
|
||||
- PK: `user_id` (`auth.users.id`, `on delete cascade`)
|
||||
- Core fields: `balance`, `frozen_balance`, `lifetime_earned`, `lifetime_spent`, `version`, `created_at`, `updated_at`
|
||||
- Constraints:
|
||||
- all numeric totals must be non-negative
|
||||
- `frozen_balance <= balance`
|
||||
|
||||
### points_ledger
|
||||
|
||||
- PK: `id`
|
||||
- FK:
|
||||
- `user_id -> auth.users.id` (`on delete cascade`)
|
||||
- `biz_id -> sessions.id` (`on delete restrict`, nullable)
|
||||
- `operator_id -> auth.users.id` (`on delete set null`)
|
||||
- Core fields: `direction`, `amount`, `balance_after`, `change_type`, `biz_type`, `biz_id`, `event_id`, `operator_id`, `metadata`, `created_at`, `updated_at`
|
||||
- Constraints:
|
||||
- `amount > 0`
|
||||
- `direction in (1, -1)`
|
||||
- `balance_after >= 0`
|
||||
- `change_type in ('register', 'consume', 'grant', 'adjust')`
|
||||
- `biz_type is null or biz_type='chat'`
|
||||
- biz binding:
|
||||
- `register => biz_type is null and biz_id is null`
|
||||
- `consume/grant/adjust => biz_type='chat' and biz_id not null`
|
||||
- direction and change_type coupling:
|
||||
- `register/grant => direction = 1`
|
||||
- `consume => direction = -1`
|
||||
- `adjust => direction in (1, -1)`
|
||||
- idempotency: `unique (user_id, event_id)`
|
||||
|
||||
#### points_ledger.metadata (schema_version=1)
|
||||
|
||||
Canonical shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": 1,
|
||||
"operator_type": "user|system|admin",
|
||||
"run_id": "string",
|
||||
"request_id": "string|null",
|
||||
"charge": {
|
||||
"message_id": "uuid",
|
||||
"message_seq": 1,
|
||||
"model_code": "string",
|
||||
"input_tokens": 0,
|
||||
"output_tokens": 0,
|
||||
"cost": "0.000000"
|
||||
},
|
||||
"ext": {}
|
||||
}
|
||||
```
|
||||
|
||||
JSON constraints:
|
||||
|
||||
- Common:
|
||||
- must be object
|
||||
- `schema_version = 1`
|
||||
- `operator_type in (user, system, admin)`
|
||||
- `run_id` non-empty
|
||||
- if present, `ext` must be object
|
||||
- Per `change_type`:
|
||||
- `register`: no `charge`, and no chat binding (`biz_type/biz_id` both null)
|
||||
- `consume`: requires `charge` object with required fields
|
||||
- `grant`: no extra metadata shape requirement
|
||||
- `adjust`: requires `ext.ticket_id` non-empty
|
||||
|
||||
## Signup initialization contract
|
||||
|
||||
- Trigger: `auth.users` after insert
|
||||
- Function: `public.initialize_profile_and_points_on_signup()`
|
||||
- Side effects:
|
||||
- create `profiles` row with default settings
|
||||
- username format: `user_xxxxxx` (`x` = 6 chars from `[a-z0-9]`)
|
||||
- create `user_points` row with initial `balance=100`, `lifetime_earned=100`
|
||||
- create `points_ledger` register row:
|
||||
- `change_type='register'`
|
||||
- `biz_type=null`, `biz_id=null`
|
||||
- `amount=100`, `direction=1`, `balance_after=100`
|
||||
|
||||
### sessions
|
||||
|
||||
- PK: `id`
|
||||
- FK: `user_id -> auth.users.id`
|
||||
- Core fields: `session_type`, `job_id`, `title`, `status`, `last_activity_at`, `message_count`, `total_tokens`, `total_cost`, `state_snapshot`, `created_at`, `updated_at`, `deleted_at`
|
||||
- Constraints:
|
||||
- `session_type in ('chat', 'automation')`
|
||||
- `status in ('pending', 'running', 'completed', 'failed')`
|
||||
- `message_count/total_tokens/total_cost` non-negative
|
||||
|
||||
### messages
|
||||
|
||||
- PK: `id`
|
||||
- FK: `session_id -> sessions.id` (`on delete cascade`)
|
||||
- Core fields: `seq`, `role`, `content`, `model_code`, `tool_name`, `input_tokens`, `output_tokens`, `cost`, `latency_ms`, `visibility_mask`, `metadata`, `created_at`, `updated_at`, `deleted_at`
|
||||
- Constraints:
|
||||
- `unique (session_id, seq)`
|
||||
- `seq > 0`
|
||||
- `role in ('user', 'assistant', 'system', 'tool')`
|
||||
- token/cost non-negative
|
||||
- `latency_ms` null or non-negative
|
||||
|
||||
## Security and ownership
|
||||
|
||||
- Backend service must derive owner identity from verified auth context.
|
||||
- Client must not be trusted for `user_id`/`operator_id` ownership semantics.
|
||||
- `metadata` and `settings` must not include secrets.
|
||||
Reference in New Issue
Block a user