docs: 更新协议文档并清理过期的问题追踪文档
This commit is contained in:
@@ -269,12 +269,21 @@ WAV 音频转写。
|
||||
|
||||
---
|
||||
|
||||
## 通用错误
|
||||
## 错误约定(Agent)
|
||||
|
||||
当前实现的错误主体为 FastAPI `detail` 字段:
|
||||
Agent 路由的错误同样遵循统一 HTTP 错误契约,详见:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "..."
|
||||
}
|
||||
```
|
||||
- `docs/protocols/common/http-error-codes.md`
|
||||
|
||||
本文件只补充 Agent 相关错误码示例:
|
||||
|
||||
- `AGENT_RUN_INPUT_INVALID`
|
||||
- `AGENT_RUN_MESSAGES_INVALID`
|
||||
- `AGENT_INVALID_LAST_EVENT_ID`
|
||||
- `AGENT_SSE_CONNECTION_LIMIT`
|
||||
- `AGENT_ATTACHMENT_EMPTY`
|
||||
- `AGENT_ATTACHMENT_TOO_LARGE`
|
||||
- `AGENT_AUDIO_UNSUPPORTED_FORMAT`
|
||||
- `AGENT_AUDIO_TOO_LARGE`
|
||||
- `AGENT_AUDIO_EMPTY`
|
||||
- `AGENT_ASR_UNAVAILABLE`
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
# HTTP Error Contract (RFC7807 + Stable Codes)
|
||||
|
||||
This document is the single source of truth for backend HTTP error transport format and frontend parsing strategy.
|
||||
|
||||
## Response Format
|
||||
|
||||
All API errors must use `application/problem+json` and include RFC7807 fields.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Unprocessable Entity",
|
||||
"status": 422,
|
||||
"detail": "Validation failed",
|
||||
"code": "TODO_TITLE_REQUIRED",
|
||||
"params": {
|
||||
"field": "title"
|
||||
},
|
||||
"instance": "/api/v1/todo"
|
||||
}
|
||||
```
|
||||
|
||||
### Field Rules
|
||||
|
||||
- `code` (required for business errors): stable machine-readable code (`UPPER_SNAKE_CASE`)
|
||||
- `params` (optional): key-value values for localized message placeholders
|
||||
- `detail` (required by RFC7807): human-readable fallback/debug text
|
||||
|
||||
## Backend Rules
|
||||
|
||||
- Do not rely on free-text `detail` as the only contract.
|
||||
- New endpoints and new error branches must return stable `code`.
|
||||
- Existing branches can migrate incrementally but must prefer code-first.
|
||||
- Keep status semantics unchanged (`400/401/403/404/409/422/429/5xx`).
|
||||
|
||||
## Frontend Parsing Rules
|
||||
|
||||
- Parse in this order: `code` -> `params` -> `status` -> fallback `detail`.
|
||||
- User-facing text should come from local l10n mapping by `code`.
|
||||
- Unknown code fallback:
|
||||
1) status-based generic localized message
|
||||
2) safe fallback localized message (do not expose raw internals)
|
||||
|
||||
## Error Code Registry (Single Source of Truth)
|
||||
|
||||
This section is the canonical registry shared by backend and frontend.
|
||||
|
||||
When creating/modifying/deprecating any code, this table must be updated in the same change.
|
||||
|
||||
| Code | Domain | HTTP | Meaning |
|
||||
|---|---|---:|---|
|
||||
| `AGENT_RUN_INPUT_INVALID` | agent | 422 | Run input payload invalid |
|
||||
| `AGENT_RUN_MESSAGES_INVALID` | agent | 422 | Run messages contract invalid |
|
||||
| `AGENT_INVALID_LAST_EVENT_ID` | agent | 422 | SSE Last-Event-ID invalid |
|
||||
| `AGENT_SSE_CONNECTION_LIMIT` | agent | 429 | SSE connections exceed per-user limit |
|
||||
| `AGENT_ATTACHMENT_EMPTY` | agent | 422 | Attachment payload empty |
|
||||
| `AGENT_ATTACHMENT_TOO_LARGE` | agent | 413 | Attachment exceeds allowed size |
|
||||
| `AGENT_AUDIO_UNSUPPORTED_FORMAT` | agent | 400 | Audio content type/header unsupported |
|
||||
| `AGENT_AUDIO_TOO_LARGE` | agent | 400 | Audio exceeds allowed size |
|
||||
| `AGENT_AUDIO_EMPTY` | agent | 400 | Audio payload empty |
|
||||
| `AGENT_ASR_UNAVAILABLE` | agent | 502 | ASR dependency unavailable |
|
||||
| `AGENT_FORBIDDEN` | agent | 403 | Current user does not own target thread/session |
|
||||
| `AGENT_PAYLOAD_INVALID` | agent | 422 | Run payload or forwarded runtime mode is invalid |
|
||||
| `AGENT_ATTACHMENTS_TOO_MANY` | agent | 422 | Attachments exceed per-message limit |
|
||||
| `AGENT_SIGNED_IMAGE_URL_INVALID` | agent | 422 | Signed image URL is malformed or unverifiable |
|
||||
| `AGENT_ATTACHMENT_STORAGE_UNAVAILABLE` | agent | 503 | Attachment storage backend unavailable |
|
||||
| `AGENT_ATTACHMENT_UNSUPPORTED_TYPE` | agent | 422 | Attachment MIME type is unsupported |
|
||||
| `AGENT_ATTACHMENT_UPLOAD_FAILED` | agent | 502 | Upload to attachment storage failed |
|
||||
| `AGENT_ATTACHMENT_BUCKET_INVALID` | agent | 422 | Attachment bucket does not match allowed bucket |
|
||||
| `AGENT_ATTACHMENT_PATH_SCOPE_INVALID` | agent | 422 | Attachment path is outside allowed user scope |
|
||||
| `AGENT_SIGNED_URL_GENERATION_FAILED` | agent | 502 | Failed to generate signed URL from storage backend |
|
||||
| `AGENT_SESSION_ID_INVALID` | agent | 422 | Session ID is not a valid UUID |
|
||||
| `AGENT_SESSION_NOT_FOUND` | agent | 404 | Agent chat session does not exist |
|
||||
| `AGENT_USER_ID_INVALID` | agent | 422 | User ID is not a valid UUID |
|
||||
| `INVALID_BINARY_URL_HOST` | agent | 422 | Signed URL host is invalid |
|
||||
| `INVALID_BINARY_URL_BUCKET` | agent | 422 | Signed URL bucket is invalid |
|
||||
| `INVALID_BINARY_URL_PATH_SCOPE` | agent | 422 | Signed URL path scope is invalid |
|
||||
| `AUTH_SERVICE_UNAVAILABLE` | auth | 503 | Upstream auth service is temporarily unavailable |
|
||||
| `AUTH_TOO_MANY_REQUESTS` | auth | 429 | Auth operation exceeds request rate limit |
|
||||
| `AUTH_VERIFICATION_CODE_INVALID` | auth | 401 | OTP verification code is invalid |
|
||||
| `AUTH_REFRESH_TOKEN_INVALID` | auth | 401 | Refresh token is invalid or expired |
|
||||
| `AUTH_REFRESH_TOKEN_MISSING` | auth | 401 | Refresh token is missing for logout/refresh |
|
||||
| `AUTH_USER_NOT_FOUND` | auth | 404 | User lookup by phone returns no match |
|
||||
| `AUTH_UNAUTHORIZED` | auth | 401 | Authorization header or token is invalid |
|
||||
| `JWT_VERIFIER_NOT_CONFIGURED` | auth | 503 | JWT verifier configuration is missing |
|
||||
| `AUTOMATION_JOB_LIMIT_EXCEEDED` | automation_jobs | 400 | User-created automation jobs exceed allowed limit |
|
||||
| `AUTOMATION_SYSTEM_JOB_MODIFICATION_FORBIDDEN` | automation_jobs | 403 | System bootstrap job cannot be modified |
|
||||
| `AUTOMATION_JOB_NOT_FOUND` | automation_jobs | 404 | Target automation job does not exist or is not owned by user |
|
||||
| `AUTOMATION_JOB_STORE_UNAVAILABLE` | automation_jobs | 503 | Automation job persistence unavailable |
|
||||
| `NOT_FOUND` | runtime/tooling | 404 | Resource/tool target not found |
|
||||
| `LOOKUP_FAILED` | runtime/tooling | 500 | Lookup or resolution failed |
|
||||
| `INTERNAL_ERROR` | runtime/tooling | 500 | Internal execution error |
|
||||
| `MISSING_RUNTIME_ARGS` | runtime/tooling | 400 | Required runtime arguments missing |
|
||||
| `TOOL_PENDING_APPROVAL` | runtime/tooling | 409 | Tool call awaiting approval |
|
||||
| `TOOL_REJECTED` | runtime/tooling | 403 | Tool call rejected by policy/user |
|
||||
| `USER_STORE_UNAVAILABLE` | users | 503 | User storage or database access unavailable |
|
||||
| `USER_NOT_FOUND` | users | 404 | Requested user profile not found |
|
||||
| `USER_UPDATE_FIELDS_EMPTY` | users | 400 | Update request contains no writable fields |
|
||||
| `USER_AVATAR_UNSUPPORTED_TYPE` | users | 422 | Avatar MIME type is unsupported |
|
||||
| `USER_AVATAR_TOO_LARGE` | users | 413 | Avatar file size exceeds configured limit |
|
||||
| `USER_AVATAR_EMPTY` | users | 422 | Avatar upload payload is empty |
|
||||
| `USER_AVATAR_UPLOAD_FAILED` | users | 502 | Upstream storage upload failed |
|
||||
| `USER_AUTH_LOOKUP_UNAVAILABLE` | users | 503 | Auth/identity phone lookup backend unavailable |
|
||||
| `TODO_SERVICE_UNAVAILABLE` | todo | 503 | Todo persistence unavailable |
|
||||
| `TODO_NOT_FOUND` | todo | 404 | Todo item does not exist |
|
||||
| `TODO_ACCESS_FORBIDDEN` | todo | 403 | Current user cannot operate on target todo |
|
||||
| `TODO_REORDER_DUPLICATE_ID` | todo | 400 | Reorder payload contains duplicate todo IDs |
|
||||
| `TODO_STATUS_INVALID` | todo | 400 | Todo status filter value invalid |
|
||||
| `TODO_PRIORITY_INVALID` | todo | 400 | Todo priority filter value out of range |
|
||||
| `SCHEDULE_ITEM_INVALID_TIME_RANGE` | schedule_items | 400 | `end_at` must be after `start_at` |
|
||||
| `SCHEDULE_ITEM_STORE_UNAVAILABLE` | schedule_items | 503 | Schedule item persistence unavailable |
|
||||
| `SCHEDULE_ITEM_NOT_FOUND` | schedule_items | 404 | Schedule item does not exist |
|
||||
| `SCHEDULE_ITEM_START_AT_TIMEZONE_REQUIRED` | schedule_items | 400 | `start_at` must include timezone when `end_at` is set |
|
||||
| `SCHEDULE_ITEM_PAGE_INVALID` | schedule_items | 400 | Pagination `page` must be greater than or equal to 1 |
|
||||
| `SCHEDULE_ITEM_PAGE_SIZE_INVALID` | schedule_items | 400 | Pagination `page_size` out of allowed range |
|
||||
| `SCHEDULE_ITEM_SHARE_FORBIDDEN` | schedule_items | 403 | Current user cannot share this schedule item |
|
||||
| `SCHEDULE_ITEM_SHARE_PERMISSION_EXCEEDED` | schedule_items | 403 | Requested share permission exceeds inviter permission |
|
||||
| `SCHEDULE_ITEM_SUBSCRIPTION_ALREADY_ACTIVE` | schedule_items | 400 | Recipient already has active subscription |
|
||||
| `SCHEDULE_ITEM_INVITE_ALREADY_SUBSCRIBED` | schedule_items | 400 | Recipient already accepted calendar invite |
|
||||
| `SCHEDULE_ITEM_INVITE_ALREADY_PENDING` | schedule_items | 400 | Recipient already has pending calendar invite |
|
||||
| `SCHEDULE_ITEM_AUTH_LOOKUP_UNAVAILABLE` | schedule_items | 503 | Auth/identity lookup unavailable when sharing |
|
||||
| `SCHEDULE_ITEM_PENDING_INVITE_NOT_FOUND` | schedule_items | 404 | No pending invitation exists for target item/user |
|
||||
| `SCHEDULE_ITEM_ACCEPT_SUBSCRIPTION_FAILED` | schedule_items | 503 | Subscription accept flow failed unexpectedly |
|
||||
| `SCHEDULE_ITEM_REJECT_SUBSCRIPTION_FAILED` | schedule_items | 503 | Subscription reject flow failed unexpectedly |
|
||||
| `SCHEDULE_ITEM_DATETIME_TIMEZONE_REQUIRED` | schedule_items | 400 | Datetime input must include timezone |
|
||||
| `SCHEDULE_ITEM_DATETIME_REQUIRED` | schedule_items | 400 | Required datetime input missing |
|
||||
| `INBOX_MESSAGE_NOT_FOUND` | inbox_messages | 404 | Inbox message does not exist for current user |
|
||||
| `INBOX_MESSAGE_STORE_UNAVAILABLE` | inbox_messages | 503 | Inbox message persistence unavailable |
|
||||
| `MEMORIES_USER_NOT_FOUND` | memories | 404 | User memory record does not exist |
|
||||
| `MEMORIES_WORK_NOT_FOUND` | memories | 404 | Work memory record does not exist |
|
||||
| `MEMORIES_SERVICE_UNAVAILABLE` | memories | 503 | Memories persistence unavailable |
|
||||
| `FRIEND_REQUEST_SELF_NOT_ALLOWED` | friendships | 400 | User cannot send friend request to self |
|
||||
| `FRIEND_ALREADY_ACCEPTED` | friendships | 400 | Users are already friends |
|
||||
| `FRIEND_REQUEST_BLOCKED` | friendships | 400 | Friend request blocked by relationship status |
|
||||
| `FRIEND_REQUEST_ALREADY_SENT` | friendships | 400 | Pending friend request already exists |
|
||||
| `FRIENDSHIP_SERVICE_UNAVAILABLE` | friendships | 503 | Friendship persistence unavailable |
|
||||
| `FRIEND_REQUEST_NOT_FOUND` | friendships | 404 | Friend request record not found |
|
||||
| `FRIEND_REQUEST_FORBIDDEN` | friendships | 403 | Current user is not allowed for this friend request action |
|
||||
| `FRIEND_REQUEST_NOT_PENDING` | friendships | 400 | Friend request is not in pending state |
|
||||
| `FRIEND_INBOX_MESSAGE_NOT_FOUND` | friendships | 404 | Friend request inbox message not found |
|
||||
| `FRIENDSHIP_DATA_INVALID` | friendships | 400 | Friendship record is missing required linkage fields |
|
||||
| `FRIENDSHIP_NOT_FOUND` | friendships | 404 | Friendship record not found |
|
||||
| `FRIENDSHIP_REMOVE_REQUIRES_ACCEPTED` | friendships | 400 | Only accepted friendships can be removed |
|
||||
|
||||
## Registry Coverage Check Script
|
||||
|
||||
Use the checker script to ensure this registry and frontend code mapping stay aligned:
|
||||
|
||||
```bash
|
||||
python3 scripts/check_error_code_registry.py
|
||||
```
|
||||
|
||||
Optional arguments:
|
||||
|
||||
- `--doc`: custom registry markdown path
|
||||
- `--mapper`: custom frontend mapper path (default: `apps/lib/core/network/error_code_mapper.dart`)
|
||||
|
||||
Output always includes three result groups:
|
||||
|
||||
- doc has code but frontend has no mapping
|
||||
- frontend maps code but doc has no such code
|
||||
- duplicate codes
|
||||
|
||||
Exit code policy:
|
||||
|
||||
- `0`: no inconsistency found
|
||||
- non-`0`: at least one inconsistency found or input path invalid
|
||||
|
||||
## Agent Error Code Set
|
||||
|
||||
### Agent
|
||||
|
||||
- `AGENT_RUN_INPUT_INVALID`
|
||||
- `AGENT_RUN_MESSAGES_INVALID`
|
||||
- `AGENT_INVALID_LAST_EVENT_ID`
|
||||
- `AGENT_SSE_CONNECTION_LIMIT`
|
||||
- `AGENT_ATTACHMENT_EMPTY`
|
||||
- `AGENT_ATTACHMENT_TOO_LARGE`
|
||||
- `AGENT_AUDIO_UNSUPPORTED_FORMAT`
|
||||
- `AGENT_AUDIO_TOO_LARGE`
|
||||
- `AGENT_AUDIO_EMPTY`
|
||||
- `AGENT_ASR_UNAVAILABLE`
|
||||
- `AGENT_FORBIDDEN`
|
||||
- `AGENT_PAYLOAD_INVALID`
|
||||
- `AGENT_ATTACHMENTS_TOO_MANY`
|
||||
- `AGENT_SIGNED_IMAGE_URL_INVALID`
|
||||
- `AGENT_ATTACHMENT_STORAGE_UNAVAILABLE`
|
||||
- `AGENT_ATTACHMENT_UNSUPPORTED_TYPE`
|
||||
- `AGENT_ATTACHMENT_UPLOAD_FAILED`
|
||||
- `AGENT_ATTACHMENT_BUCKET_INVALID`
|
||||
- `AGENT_ATTACHMENT_PATH_SCOPE_INVALID`
|
||||
- `AGENT_SIGNED_URL_GENERATION_FAILED`
|
||||
- `AGENT_SESSION_ID_INVALID`
|
||||
- `AGENT_SESSION_NOT_FOUND`
|
||||
- `AGENT_USER_ID_INVALID`
|
||||
|
||||
## Compatibility Strategy
|
||||
|
||||
- Transition phase keeps `detail` and adds `code`/`params`.
|
||||
- Frontend moves to code-first mapping first; backend can then continue migrating remaining endpoints.
|
||||
Reference in New Issue
Block a user