跳到主要内容

FinClaw V1 API Contract Design

状态:Accepted Initial Design / B-3 工程蓝图 日期:2026-05-16 项目:FinClaw 文档级别:项目级 API / SSE 契约 上游文档:v1-prd.md §6 / §7v1-product-object-and-schema-design.md §3 ~ §13v1-agent-orchestration-design.md §2 / §10A / §10Bv1-ui-ux-interaction-design.md §2 / §6A / §10v1-commercial-signal-instrumentation-design.md §3 / §4 / §9 配套文档:v1-tech-stack-and-architecture-design.md(B-1)、v1-data-and-persistence-design.md(B-2)

1. Purpose

本文是 FinClaw V1 工程仓库 /Users/mlabs/Programs/CurvatureLabs/finclaw/ 后端对前端(以及未来对外部系统)的 API 契约 single source of truth

回答 4 个工程问题:

  1. 全部 REST endpoint 的 URL / method / request / response 是什么?
  2. ReAct 期间通过 SSE 推给前端的事件清单是什么?payload 是什么?
  3. V1 内测的鉴权与隔离怎么做?
  4. 错误、限流、跨对象引用的统一约定是什么?

本文重复 v1-product-object-and-schema-design.md 的字段定义;API 层只负责把对象 schema 暴露为 HTTP shape。

2. API Style and Versioning

2.1 API 风格

维度选型
协议HTTP/1.1(Trial 期间 dev 用 HTTP;外发 dev 链接前升 HTTPS)
风格RESTful(CRUD + 行为类 sub-resource)
编码UTF-8 JSON(除 SSE)
时间格式ISO-8601 + UTC(Z 后缀)
Streamingtext/event-stream(SSE);不引入 WebSocket
OpenAPIFastAPI 自动生成 /api/openapi.json(dev 暴露;trial-prod 关闭)
Snake / Camelrequest / response snake_case,与 Pydantic 字段一致
文件上传V1 不支持(mvp-product-definition.md §15 不引入文件上传)

2.2 路径前缀

/api/<resource> ← V1 默认(无 v1 前缀)

V1 内测期间所有 endpoint 不带 /v1/ 前缀:内测 cohort 仅 3 人,破坏性变更可直接通知。trial 退出 / 公开扩展时再升级到 /api/v1/...。本文所有 path 写 /api/...,工程实现时可全局替换。

2.3 HTTP 状态码语义

Code用途
200同步成功(含读、行为类 mutation)
201POST 创建成功 + 返回新对象
202任务已接收,进入 SSE 异步流(POST /api/tasks)
204成功无返回体(PATCH consent revoke 等)
400请求 schema 错误
401未鉴权
403已鉴权但无权限(含 BoundaryGuard 拒绝)
404对象不存在
409状态冲突(如 thread 已 closed 不能 refresh)
422Pydantic 字段校验失败(FastAPI 默认)
429限流(§8)
451Sensitive input rejection(§7.2 RB-2 类)
500未捕获异常
503Kill switch 触发 / dependency unavailable

2.4 Idempotency

类型策略
GET天然幂等
DELETE幂等(重复 delete 返回 204)
PATCH幂等(同 payload 多次 PATCH 等价于一次)
POST /api/tasks幂等;客户端可传 Idempotency-Key header,5 分钟内重复 key 返回上次的 task_id
POST /api/threads/{id}/refresh同上:Idempotency-Key 5 分钟窗口

3. Authentication

3.1 V1 鉴权方案

V1 选型
方案Bearer Token(每位内测用户一枚 long-lived token)
HeaderAuthorization: Bearer <token>
Token 来源trial 启动时 python -m server.scripts.seed_user --user_id=alice 生成;分发渠道为 Labs 内部协作(v1-engineering-kickoff-decisions.md D-01
Token 存储后端:data/_internal/tokens.json(仅项目发起人可读,git ignored);前端:浏览器 localStorage["finclaw_token"]
Token 撤回python -m server.scripts.revoke_user_token --user_id=alice 立即生效
Token 旋转V1 不实现自动旋转;如泄露立即手动 revoke + reissue

3.2 Anonymous(仅特定 endpoint)

Endpoint允许匿名?
GET /api/health
GET /api/openapi.jsondev 是;trial-prod 否
其余全部

3.3 用户隔离

  • 所有 user-scoped endpoint 的 path 含 {user_id} 时,必须与 token 关联的 user_id 一致;不一致返回 403;
  • 行为类 endpoint(POST /api/tasks 等)不在 path 中带 user_id;后端从 token 解析 user_id,所有数据写入 data/cognition/<user_id>/...v1-data-and-persistence-design.md §4.1);
  • 项目发起人有特殊 admin token(scope: admin),可访问 /api/_admin/* 端点(v1-data-and-persistence-design.md §4.1 _internal 路径)。

3.4 后续 OAuth 升级路径预留

V1 之后切到 OAuth:

  • 当前 Bearer token 中间件抽象为 Auth: Protocol
  • 升级时增加 OAuth2Auth 实现,token 提取逻辑替换;
  • API path / response shape 不变。

4. REST Endpoints

4.1 端点总览

MethodPath用途
GET/api/health健康检查
POST/api/tasks创建任务 → 进入 SSE 流
GET/api/tasks/{task_id}查询任务最终状态(fallback;SSE 是主路径)
GET/api/snapshots/{snapshot_id}读取 snapshot
POST/api/snapshots/{snapshot_id}/save-as-thread从 snapshot 创建 thread
GET/api/snapshots列出当前用户 snapshot(分页)
GET/api/threads/{thread_id}读取 thread header + linked snapshots refs
GET/api/threads/{thread_id}/history读取 thread refresh history(分页)
POST/api/threads/{thread_id}/refresh触发 refresh → 进入 SSE 流
PATCH/api/threads/{thread_id}修改 thread 元数据(status / title / refresh_conditions)
GET/api/threads列出当前用户 thread(分页)
GET/api/checkpoints/{checkpoint_id}读取 checkpoint
GET/api/evidence/{evidence_id}读取单条 evidence
GET/api/snapshots/{snapshot_id}/evidence列出 snapshot 关联 evidence 与 quality notes
POST/api/feedback提交反馈 / 请求人工复核
GET/api/users/me当前用户基础信息(user_id / scope / created_at)
GET/api/users/{user_id}/profile-consent读取 ProfileConsent 当前状态
POST/api/users/{user_id}/profile-consent创建 / 更新 ProfileConsent
DELETE/api/users/{user_id}/profile-consent/{consent_id}撤回 consent(不删账号)
POST/api/events/cs前端上报商业信号事件(v1-commercial-signal-instrumentation-design.md §4
POST/api/events/cs/batch批量上报(前端 buffer flush)

Admin-only:

MethodPath用途
GET/api/_admin/feedback/queue人工复核待处理列表
POST/api/_admin/feedback/{feedback_id}/reviewreviewer 提交 grade
POST/api/_admin/kill-switch触发 / 解除 kill switch

4.2 详细 schema

字段命名与 v1-product-object-and-schema-design.md 1:1 对齐;下文仅展示 envelope 形态与 V1 必要字段。完整字段定义以 schema 设计文档为准。

4.2.1 GET /api/health

response_200:
status: "ok"
version: "<git_short_sha>"
build_time: "<iso8601>"
kill_switch_active: false
providers:
primary: "gpt-5.5"
secondary: "kimi-k2.6"

4.2.2 POST /api/tasks

请求:

request:
question: string # 用户自然语言输入;必填
hint_task_type: enum | null # snapshot / thread_refresh / risk_challenge / pre_execution / null
thread_ref: string | null # 若是 thread refresh / continuation;格式见 §6
snapshot_ref: string | null # 若基于已有 snapshot 提问
user_context_overrides: # 可选:本次任务覆盖 saved profile
research_depth: enum | null
risk_prompt_preference: enum | null
language_preference: enum | null
client_metadata:
client: web | mobile-web
client_version: string

Header:Idempotency-Key: <client-generated-uuid>(可选)

响应:

response_202:
task_id: "task_<iso>_<hash>"
sse_url: "/api/tasks/task_xxx/stream" # 前端立即订阅
estimated_ms: integer | null

错误:

状态触发
400question 为空 / 超长(> 4000 字符)
451Sensitive Input Classifier 在前置检查中检测到凭证 / 私钥(详见 §7.2)
503kill switch 触发

4.2.3 GET /api/tasks/{task_id}/stream(SSE)

详见 §5。

4.2.4 GET /api/tasks/{task_id}

response_200:
task_id: string
status: enum # running / completed / failed / cancelled
task_type: enum
created_at: datetime
completed_at: datetime | null
produced_object_refs: # 任务产出的 object refs(§6)
- "snapshot:snap_xxx"
- "checkpoint:chk_xxx"
failure:
code: string | null
message: string | null

4.2.5 GET /api/snapshots/{snapshot_id}

response_200:
snapshot_id: string
title: string
cognition_object: object # 见 schema §3.1
task_type: enum
time_context: object
market_context: object
main_thesis: string
supporting_reasons: array
counter_thesis: array
uncertainties: array
watch_questions: array
invalidators: array
evidence_items: array # 简化引用 + inline summary
data_quality_notes: array
advisor_outputs: array | null
pre_execution_checkpoint_refs: array
thread_proposal: object | null
execution_boundary: string
ui_state: enum # complete / needs_clarification / low_confidence / source_limited / action_adjacent / thread_candidate
created_at: datetime
schema_version: "1.0"

response_404:
error: { code: "snapshot_not_found", message: "..." }

4.2.6 POST /api/snapshots/{snapshot_id}/save-as-thread

请求:

request:
title: string # 用户可编辑
user_focus_reason: string
research_style: object | null
refresh_conditions: array
invalidators: array
watch_questions: array | null # 默认沿用 snapshot
save_user_context: bool # 与 ProfileConsent 联动
consent_ack: # 当 save_user_context=true 时必填
consent_id: string # 引用现有 consent 或新建

响应:

response_201:
thread_id: string
thread: { ...full thread header... }

错误:

状态触发
409同 snapshot 已 save 过为 thread(返回原 thread_id)
403save_user_context=true 但无对应 consent

4.2.7 GET /api/snapshots / GET /api/threads

分页约定:

?limit=20&cursor=<opaque>&sort=created_at:desc&status=active
response_200:
items: array
next_cursor: string | null
total_known: integer | null # 文件系统不一定全量 count;可为 null

4.2.8 GET /api/threads/{thread_id}

response_200:
thread_id: string
title: string
status: enum # proposed / active / refresh_due / refreshed / paused / closed / merged
object: object
user_focus_reason: string
research_style: object | null
linked_snapshots: array # ["snapshot:snap_xxx", ...]
current_thesis: string
counter_thesis: array
watch_questions: array
refresh_conditions: array
invalidators: array
evidence_state: object
pre_execution_checkpoints: array
profile_consent_refs: array
last_refreshed_at: datetime | null
closed_reason: string | null
object_version: integer
schema_version: "1.0"

4.2.9 GET /api/threads/{thread_id}/history

response_200:
thread_id: string
changes: # 一行一个 RefreshChange,见 schema §4.3
- refresh_id: string
trigger_type: enum
previous_snapshot_ref: string
new_snapshot_ref: string
new_facts: array
changed_inferences: array
risk_changes: array
evidence_changes: array
watch_question_updates: array
execution_boundary_update: string | null
created_at: datetime
next_cursor: string | null

4.2.10 POST /api/threads/{thread_id}/refresh

请求:

request:
trigger_type: enum # user / condition / time / evidence / counter_thesis
user_supplied_context: string | null

响应:

response_202:
task_id: "task_xxx"
sse_url: "/api/tasks/task_xxx/stream"

错误:

状态触发
409thread.status ∈ merged

4.2.11 PATCH /api/threads/{thread_id}

请求(仅传待修改字段):

request:
status: enum | null # 可改:active / paused / closed
closed_reason: string | null # status=closed 时必填
title: string | null
refresh_conditions: array | null
invalidators: array | null

响应:

response_200:
thread: { ...full thread header... }

4.2.12 GET /api/checkpoints/{checkpoint_id}

response_200:
checkpoint_id: string
source_question: string
normalized_cognition_task: string
related_snapshot_ref: string | null
related_thread_ref: string | null
strategy_hypothesis: object | null
supporting_conditions: array
risk_constraints: array
invalidators: array
user_confirmation_needed: array
data_quality_notes: array
forbidden_execution_fields: array # 必须包含;提示前端不渲染对应控件
non_execution_statement: string
created_at: datetime
schema_version: "1.0"

API 层硬约束:response 中必须包含 forbidden_execution_fields 字段(即使为空数组),且永不包含 order_* / quantity / leverage / account / wallet_* / private_key / api_key / auto_execute / alert_trigger_to_execute 字段(v1-product-object-and-schema-design.md §5.2)。

4.2.13 GET /api/evidence/{evidence_id} / GET /api/snapshots/{snapshot_id}/evidence

response_200:
evidence_id: string
claim_ref: string # §6
source_type: enum
source_ref: string | null
observed_at: datetime | null
retrieved_at: datetime | null
evidence_status: enum
limitation: string
schema_version: "1.0"

/api/snapshots/{snapshot_id}/evidence 返回 { evidence_items: [...], data_quality_notes: [...] }

4.2.14 POST /api/feedback

request:
feedback_kind: enum # quick_thumbs / detailed_review_request / boundary_concern / sensitive_concern
related_object_ref: string # §6
rating: enum | null # helpful / partially_helpful / not_helpful / needs_review
comment: string | null
request_human_review: bool
client_metadata: object

response_201:
feedback_id: string
human_review_queued: bool

4.2.15 ProfileConsent endpoints

GET /api/users/{user_id}/profile-consent

response_200:
consent_id: string # 当前生效版本
context_ref: string
save_to_profile: bool
save_to_thread: bool
training_use_allowed: bool
de_identified: bool
retention_scope: enum
delete_or_revoke_available: true
confirmed_at: datetime
consent_version: integer
consent_history:
- { consent_id: string, confirmed_at: datetime, revoked_at: datetime | null }

POST /api/users/{user_id}/profile-consent

request:
context_ref: string
save_to_profile: bool
save_to_thread: bool
training_use_allowed: bool
de_identified: bool
retention_scope: enum # current_task / thread / profile / none
consent_text_version: string # 用户看到的同意书版本号

response_201:
consent_id: string
consent_version: integer

DELETE /api/users/{user_id}/profile-consent/{consent_id}

response_204: (no body)
side_effects: # 见 v1-data-and-persistence-design §6.5
- "已生成的 Snapshot / Thread / Checkpoint 保留"
- "后续 LLM 调用不再注入 saved profile"
- "Commercial signal events 自此刻起停止采集;历史 events 48h 内删除"

4.2.16 商业信号事件 endpoint

POST /api/events/cs:单条上报;POST /api/events/cs/batch:批量。

request_single:
event_name: string # 见 cs-instr §4
event_category: enum
ts: datetime
related_object_refs: object
payload: object
client_metadata: object

response_202:
event_id: string
accepted: bool # 若 ProfileConsent.consent_for_trial_data=false → false(不入库)

后端在落库前必须读取 ProfileConsent 验证 consent_state(v1-commercial-signal-instrumentation-design.md §3)。

4.2.17 Admin endpoints

GET /api/_admin/feedback/queue → 待复核 feedback 列表
POST /api/_admin/feedback/{id}/review → { grade, reviewer_notes, action }
POST /api/_admin/kill-switch → { action: "engage" | "release", reason }

scope: admin token 可访问;非 admin 一律 403。

5. SSE Events Catalog

5.1 SSE 协议约定

GET /api/tasks/{task_id}/stream
Accept: text/event-stream
Authorization: Bearer <token>

→ stream:
event: task_started
id: 1
data: {"task_id": "...", "task_type": "snapshot", "ts": "..."}

event: cognition_step
id: 2
data: {...}

event: task_completed
id: N
data: {...}
字段描述
event事件名(见 §5.2)
id单调递增(断线重连时客户端可发 Last-Event-ID header,后端 best-effort 重放未确认事件,V1 不保证完整重放)
dataJSON payload
retry: 5000第一条 message 中携带,告知客户端重连间隔(ms)

5.2 事件清单

Event触发点Payload 关键字段
task_started任务进入 ReAct looptask_id, task_type, route, ts
cognition_stepReAct 每一步推进step_index, step_kind (task_routed / context_built / advisor_planned / skill_running / evidence_audited / object_drafting), summary
tool_called内部 tool 被调用tool_name, args_summary, started_at, duration_ms(tool 执行完成后发)
advisor_invokedAdvisor 视角进入advisor_role, provider_used, coordination_mode (sequential / parallel-then-merge / challenge-after-draft,v1-agent-orchestration-design.md §10A)
boundary_eventBoundaryGuard 阻挡或重写event_kind (forbidden_field_stripped / action_adjacent_redirected / sensitive_input_rejected), details
snapshot_completedSnapshot 写盘完成snapshot_id, ui_state, has_thread_proposal, has_checkpoint_ref
checkpoint_completedCheckpoint 写盘完成checkpoint_id, linked_snapshot_ref
thread_refreshedThread refresh 写盘完成thread_id, refresh_id, change_summary(new_facts_count / changed_inferences_count / risk_changes_count / evidence_changes_count)
evidence_updated证据收集阶段产出新 evidence_items(增量更新 UI)evidence_id, claim_ref, evidence_status
data_quality_updated同上,但是 data_quality_notesquality_id, applies_to, quality_state
task_completed任务正常结束task_id, produced_object_refs, final_ui_state, total_duration_ms
task_failed任务异常结束task_id, error: { code, message, retriable }, partial_object_refs
degradation_noticeAgent 进入降级路径(v1-agent-orchestration-design.md §10Bdegradation_kind, affected_field, user_visible_message

5.3 Sequence 模式(Snapshot 任务示例)

task_started
cognition_step (task_routed: snapshot)
cognition_step (context_built)
cognition_step (advisor_planned: 3 advisors)
advisor_invoked (Asset Research, gpt-5.5, sequential)
tool_called (crypto_data.get_market_overview)
evidence_updated (claim_ref: ...#main_thesis)
advisor_invoked (Market/Macro, kimi-k2.6, parallel-then-merge)
advisor_invoked (Counter-Thesis, kimi-k2.6, challenge-after-draft)
data_quality_updated
boundary_event (action_adjacent_redirected) | optional
cognition_step (object_drafting)
snapshot_completed
[checkpoint_completed] ← 仅 action_adjacent 时
task_completed

5.4 SSE 边界与降级

场景行为
客户端断线服务端继续完成任务并写盘;用户重新进入页面后用 GET /api/tasks/{task_id} + GET /api/snapshots/{snapshot_id} 拉取最终结果
服务端崩溃当前任务标记 failed;客户端再次连接 /stream 收到 task_failed
Idle timeout60 秒无事件后服务端发 event: heartbeat\ndata: {}\n\n;任务平均时长 < 60 秒,超时不应发生
流被 nginx bufferednginx 配置 proxy_buffering off; X-Accel-Buffering no;

6. Object Reference Conventions

6.1 引用语法

<object_type>:<object_id>[#<jsonpath>]
含义
snapshot:snap_2026-05-18T10-23-04Z_a3f5b2整个 snapshot
snapshot:snap_xxx#supporting_reasons[2]snapshot 的第 3 条 supporting reason
thread:thr_xxx#current_thesisthread 当前主判断
checkpoint:chk_xxx整个 checkpoint
evidence:evi_xxx单条 evidence
feedback:fb_xxx单条 feedback
user:alice用户

6.2 跨对象引用清单

出现位置字段引用类型
Snapshotpre_execution_checkpoint_refs[]checkpoint:...
Snapshotevidence_items[].claim_refsnapshot:<self>#supporting_reasons[N]#counter_thesis[N]
Threadlinked_snapshots[]snapshot:...
Threadpre_execution_checkpoints[]checkpoint:...
Threadprofile_consent_refs[]consent:...
Checkpointrelated_snapshot_ref / related_thread_refsnapshot:... / thread:...
Feedbackrelated_object_ref任意
CommercialSignalEventrelated_object_refs.{snapshot_id, thread_id, checkpoint_id}

6.3 解析规则

后端 CognitionStore.resolve(ref) 实现:

  1. split on :(object_type, rest)
  2. split rest on #(object_id, jsonpath)
  3. lookup file under data/cognition/<user_id>/<object_type plural>/<object_id>.json
  4. 若有 jsonpath,应用 jsonpath 解析;
  5. 找不到返回 null(不 raise)。

6.4 跨用户引用

V1 禁止跨用户引用。引用格式不含 user_id;若 resolve 时 lookup 不到(因为引用来自另一用户的对象),返回 null

7. Error Schema

7.1 统一错误对象

所有非 2xx / 非 SSE 错误都返回:

{
"error": {
"code": string, # snake_case 稳定标识符
"message": string, # 人类可读
"details": object | null, # 可选;具体字段错误等
"trace_id": string | null, # server-side trace id(traces/ 中可查)
"retriable": bool,
"user_visible": bool # 是否可直接给用户看(false 时前端用兜底文案)
}
}

7.2 错误码清单

CodeHTTPretriableuser_visible触发
unauthenticated401falsetrue无 token / token 无效
unauthorized403falsetruetoken 无该 user / 该 endpoint 权限
forbidden_admin_only403falsefalse非 admin 访问 /api/_admin/*
not_found404falsetrue对象不存在
validation_error422falsefalsePydantic 字段校验失败;details 含字段路径
state_conflict409falsetruethread 已 closed 等
idempotency_key_in_use409falsefalse同 key 任务已在跑
boundary_block403falsetrueBoundaryGuard 拒绝(含 forbidden execution field)
sensitive_input_rejected451falsetrueSensitive Input Classifier 检测到 credential / private key
provider_failure502truefalse主备 provider 全部失败(v1-model-and-provider-policy.md §6
quota_exceeded429true after cooldowntrueper-user / daily / monthly budget hit
kill_switch_active503true after releasetruedata/.kill_switch 存在
degraded_no_advisor_output502truetrue全部 advisor 都未产出可用结构化输出
internal_error500truefalse未捕获异常

7.3 SSE 错误传播

SSE 中错误通过 task_failed 事件传,断流;payload 中含同样的 error 对象。

7.4 Sensitive input rejection 细则

v1-product-object-and-schema-design.md §9 credential 类输入:

response_451:
error:
code: "sensitive_input_rejected"
message: "FinClaw does not accept credentials, private keys, exchange keys, or wallet recovery phrases."
user_visible: true
details:
classification: "credential_or_permission"
input_segment_index: integer # 用户输入中疑似命中的段索引
action: "rejected_no_save_no_train_no_echo"

事件同时记录到 data/sensitive/<user_id>/snh_xxx.json(仅 metadata,v1-data-and-persistence-design.md §9)。

8. Rate Limiting

8.1 V1 限流策略(per-user)

维度Limit滑窗
POST /api/tasks30 / hour1 小时滑窗
POST /api/threads/{id}/refresh20 / hour
POST /api/feedback60 / hour
POST /api/events/cs600 / 5 min5 分钟滑窗(前端 buffer flush 友好)
其余 GET1200 / hour1 小时滑窗(read-heavy 可放宽)
Admin endpoints不限

V1 简化实现:内存 token bucket(per user_id);服务重启后重置(trial 内可接受)。

8.2 触达 limit 的响应

HTTP/1.1 429 Too Many Requests
Retry-After: <seconds>
{
"error": {
"code": "quota_exceeded",
"message": "Rate limit exceeded for create_task; retry in 120s.",
"user_visible": true,
"retriable": true,
"details": { "limit": 30, "window_seconds": 3600, "scope": "create_task" }
}
}

8.3 Cost-based budget(与 §7 quota_exceeded 共享 code)

v1-model-and-provider-policy.md §7

  • per-snapshot hard cap $0.10:超出立即 BoundaryGuard 拒绝,返回 quota_exceeded + details.scope: "per_snapshot_cost"
  • daily / monthly budget 触达:整个工程仓库降级为「只读模式」——所有 mutation endpoint 返回 kill_switch_active,但 reads 继续。

8.4 不实现

  • per-IP 限流(V1 内测无 abuse 风险);
  • 地理 / 语言 限流;
  • API key tier-based 限流(V1 没有付费层)。

9. Frontend Adapter Contract

9.1 前端 client 抽象

v1-tech-stack-and-architecture-design.md §4:前端用 zod schema 镜像后端 Pydantic 模型;REST 调用统一通过 web/src/api/client.ts

// pseudocode
class FinclawClient {
constructor(opts: { baseUrl: string; getToken: () => string });
health(): Promise<HealthResponse>;
createTask(req: CreateTaskRequest, opts?: { idempotencyKey?: string }): Promise<CreateTaskResponse>;
streamTask(taskId: string, handlers: SSEHandlers): EventSource;
getSnapshot(snapshotId: string): Promise<Snapshot>;
saveSnapshotAsThread(snapshotId: string, req: SaveAsThreadRequest): Promise<Thread>;
listSnapshots(opts?: ListOpts): Promise<Page<SnapshotIndex>>;
getThread(threadId: string): Promise<Thread>;
getThreadHistory(threadId: string, opts?: ListOpts): Promise<Page<RefreshChange>>;
refreshThread(threadId: string, req: RefreshRequest, opts?: { idempotencyKey?: string }): Promise<CreateTaskResponse>;
patchThread(threadId: string, patch: ThreadPatch): Promise<Thread>;
getCheckpoint(checkpointId: string): Promise<Checkpoint>;
getEvidence(evidenceId: string): Promise<Evidence>;
getProfileConsent(userId: string): Promise<ProfileConsent>;
upsertProfileConsent(userId: string, req: ProfileConsentRequest): Promise<ProfileConsent>;
revokeConsent(userId: string, consentId: string): Promise<void>;
postFeedback(req: FeedbackRequest): Promise<FeedbackResponse>;
reportEvent(event: CommercialSignalEvent): Promise<void>; // fire-and-forget
reportEvents(events: CommercialSignalEvent[]): Promise<void>;
}

9.2 SSE 订阅契约

type SSEHandlers = {
onTaskStarted?(e: TaskStartedEvent): void;
onCognitionStep?(e: CognitionStepEvent): void;
onToolCalled?(e: ToolCalledEvent): void;
onAdvisorInvoked?(e: AdvisorInvokedEvent): void;
onBoundaryEvent?(e: BoundaryEvent): void;
onSnapshotCompleted?(e: SnapshotCompletedEvent): void;
onCheckpointCompleted?(e: CheckpointCompletedEvent): void;
onThreadRefreshed?(e: ThreadRefreshedEvent): void;
onEvidenceUpdated?(e: EvidenceUpdatedEvent): void;
onDataQualityUpdated?(e: DataQualityUpdatedEvent): void;
onDegradationNotice?(e: DegradationEvent): void;
onTaskCompleted?(e: TaskCompletedEvent): void;
onTaskFailed?(e: TaskFailedEvent): void;
onError?(err: Error): void; // 网络层错误
};

9.3 错误处理协定

前端遇到错误对象时按 error.user_visible 决定:

user_visible行动
true直接展示 error.message
false展示通用兜底文案("出错了,请稍后再试"),把 error.code + trace_id 写入 console + report_value_articulation_submitted 不必触发

error.retriable=truecode in {provider_failure, kill_switch_active, internal_error} 时,UI 显示「再试一次」按钮(POST /api/tasks 重发,复用同一 Idempotency-Key)。

9.4 与 UI states 对齐

UI state来源
Snapshot complete / low_confidence / source_limited / action_adjacent / thread_candidate / needs_clarificationsnapshot.ui_state 字段
Thread active / refresh_due / refreshed / paused / closed / mergedthread.status 字段
Refresh diff "no material change"change_summary 各 count == 0(v1-ui-ux-interaction-design.md §6A
Sensitive rejectedHTTP 451 → 显示 v1-ui-ux-interaction-design.md §8 短文案
Trial haltedHTTP 503 + kill_switch_activev1-ui-ux-interaction-design.md §10 review needed 通用文案

9.5 商业信号事件上报

前端在以下时机调用 reportEvent / reportEvents

  • session_startedtask_submitted 等行为事件(v1-commercial-signal-instrumentation-design.md §4);
  • 上报失败(401 / 451 / 5xx)静默 retry 1 次;仍失败丢弃(不阻塞主流程);
  • 上报前不主动检查 ProfileConsent(前端不持有 consent state 副本,由后端门控)。

9.6 Frontend lint 守门

前端 CI 必须包含一条 lint 规则(自定义 ESLint / 文本规则):

  • 禁止在任何源文件中出现以下字符串(避免 UI 误渲染交易控件):
    • order_side, order_type, quantity, leverage, wallet_address, private_key, seed_phrase, auto_execute, BuySellButton, LongShortToggle, LeverageSlider, WalletConnect
  • 例外白名单:api/types.ts 中作为 forbidden_execution_fields 字段值的字符串字面量(受 Pydantic schema 约束,不渲染为控件)。

10. Acceptance

本文满足 V1 工程化 B-3 任务的接收条件:

状态
全部 REST endpoint 列出 + request/response schema(§4)
SSE 事件清单 + payload + sequence 示例(§5)
Bearer token 鉴权 + 用户隔离 + 后续 OAuth 升级路径预留(§3)
统一错误对象 + 错误码清单(§7)
Per-user rate limit 简化策略(§8)
Object reference convention 与 v1-product-object-and-schema-design.md §13 / v1-data-and-persistence-design.md §4.4 对齐
Frontend adapter contract(§9)
Checkpoint endpoint 显式禁字段约束(§4.2.12)
Sensitive rejection / kill switch 在 API 层有明确 code(§7.2)
商业信号事件 endpoint + consent 门控(§4.2.16)

11. Open Items

  • O-1:Idempotency-Key 的 5 分钟窗口实现细节(in-memory or 文件持久化) — 待 W-10 工程实现时定;
  • O-2:SSE Last-Event-ID 重放策略 V1 是否实现,还是直接告知客户端 fallback GET /api/tasks/{id} — 倾向后者(V1 简化),待与 frontend sub-packet owner 对齐;
  • O-3:admin token 是否需要单独的 Authorization: Admin <token> 双头鉴权 — V1 用 scope: admin 字段足够;待 trial 启动前最终确认;
  • O-4:商业信号事件批量上报的 batch size 上限(建议 ≤ 50) — 与 v1-commercial-signal-instrumentation-design.md 后续 sub-packet 联调;
  • O-5:/api/openapi.json 在 trial-prod 是否完全关闭还是仅对 admin 暴露 — 待 trial 启动前与项目发起人对齐。