2026-07-25 16:59:06 +08:00
|
|
|
|
# API 合约
|
|
|
|
|
|
|
|
|
|
|
|
> MVP 使用 `/api/v1`、UTF-8 JSON over HTTPS。文件上传使用 `multipart/form-data`。
|
|
|
|
|
|
> Web 页面可以直接调用同一 usecase,但不能形成不同的业务规则。
|
|
|
|
|
|
|
|
|
|
|
|
## 通用约定
|
|
|
|
|
|
|
|
|
|
|
|
- 所有 ID 为 UUID 字符串。
|
|
|
|
|
|
- 时间为 UTC ISO 8601,例如 `2026-07-25T08:30:00Z`。
|
|
|
|
|
|
- 金额使用十进制定点字符串,例如 `"199.00"`,币种固定 `CNY`;不使用浮点数。
|
|
|
|
|
|
- 写接口在合约标注处接收 `Idempotency-Key` 请求头。
|
|
|
|
|
|
- App 的任务执行接口同时需要用户 Bearer Token、设备身份和 `X-Claim-Token`。
|
|
|
|
|
|
- 分页使用 `limit` 和不透明 `cursor`;MVP `limit` 最大 100。
|
|
|
|
|
|
- 客户端不得根据 HTTP 超时判断操作失败,必须查询资源最终状态。
|
|
|
|
|
|
|
2026-07-26 15:18:48 +08:00
|
|
|
|
管理 Web/API 使用 ADMIN 服务端会话;App 执行接口使用 BUYER + 设备 Bearer token。
|
|
|
|
|
|
两种身份不能互换。HTTP 明文只允许 loopback 开发监听,非 loopback 服务必须配置
|
|
|
|
|
|
certificate/private key 并直接启用 TLS。
|
2026-07-26 14:03:32 +08:00
|
|
|
|
|
2026-07-25 16:59:06 +08:00
|
|
|
|
通用错误:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"error": {
|
|
|
|
|
|
"code": "TASK_STATE_CONFLICT",
|
|
|
|
|
|
"message": "任务当前状态不允许此操作",
|
|
|
|
|
|
"retryable": false,
|
|
|
|
|
|
"details": {}
|
|
|
|
|
|
},
|
|
|
|
|
|
"request_id": "f2cf02e9-57e0-4fca-817b-c85228acfc81"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
HTTP 语义:
|
|
|
|
|
|
|
|
|
|
|
|
- `400` 请求格式或业务校验失败
|
|
|
|
|
|
- `401` 未建立有效身份
|
|
|
|
|
|
- `403` 身份有效但无权限、设备禁用或 claim 不属于当前设备
|
|
|
|
|
|
- `404` 资源不存在,外部响应不泄露其他人的资源存在性
|
|
|
|
|
|
- `409` 幂等冲突、状态冲突或设备已有活跃任务
|
|
|
|
|
|
- `413` 文件过大
|
|
|
|
|
|
- `415` 不支持的媒体类型
|
|
|
|
|
|
- `422` 结构可解析但字段不满足 schema
|
|
|
|
|
|
- `429` 频率限制
|
|
|
|
|
|
- `503` 依赖暂不可用
|
|
|
|
|
|
|
2026-07-25 22:29:56 +08:00
|
|
|
|
## 服务健康
|
|
|
|
|
|
|
|
|
|
|
|
### `GET /healthz`
|
|
|
|
|
|
|
|
|
|
|
|
不需要业务身份,只检查进程和 SQLite 连接,不返回版本、路径、DSN、连接池统计或内部
|
|
|
|
|
|
错误。响应带 `Cache-Control: no-store`,默认不返回 CORS header。
|
|
|
|
|
|
|
|
|
|
|
|
数据库可用:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"status":"ok"}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
返回 `200`。数据库不可用时返回 `503`:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"status":"unavailable"}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
健康检查失败不能终止进程;未知路由和不允许的方法分别使用稳定 `404`/`405` JSON。
|
|
|
|
|
|
|
2026-07-25 16:59:06 +08:00
|
|
|
|
## 认证
|
|
|
|
|
|
|
|
|
|
|
|
### 管理 Web 会话
|
|
|
|
|
|
|
|
|
|
|
|
`POST /login` 接受表单账号密码,成功后设置 `HttpOnly`、`Secure`、`SameSite=Lax`
|
2026-07-26 15:18:48 +08:00
|
|
|
|
会话 Cookie。loopback HTTP 开发时不设置 `Secure`,非 loopback 服务必须直接启用
|
|
|
|
|
|
TLS。管理会话固定 8 小时绝对有效期;`POST /logout` 撤销服务端会话并清除 Cookie。
|
|
|
|
|
|
Web 会话不能调用设备执行接口。
|
|
|
|
|
|
|
|
|
|
|
|
未登录页面请求以 `303` 跳转 `/login?next=...`;`next` 只允许 `/tasks` 及其本站
|
|
|
|
|
|
子路径。未授权管理 API 返回 `401 ADMIN_SESSION_REQUIRED`。Cookie 认证的管理 API
|
|
|
|
|
|
写请求除原 Content-Type/幂等要求外,还必须携带与 CSRF Cookie 匹配的
|
|
|
|
|
|
`X-CSRF-Token`。
|
|
|
|
|
|
|
|
|
|
|
|
`POST /login` 在通过表单和 CSRF 校验、执行 bcrypt 前,按服务端观察到的来源地址
|
|
|
|
|
|
限流。5 分钟内最多 10 次;成功登录清零。超限返回 `429`、`Retry-After` 和通用
|
|
|
|
|
|
中文提示,不暴露账号是否存在。
|
2026-07-25 16:59:06 +08:00
|
|
|
|
|
|
|
|
|
|
### `POST /api/v1/auth/token`
|
|
|
|
|
|
|
|
|
|
|
|
采购 App 建立用户和设备联合会话。
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"username": "buyer01",
|
|
|
|
|
|
"password": "local-input-only",
|
|
|
|
|
|
"device_id": "f0c3d438-1c44-4f80-b898-c645afe7eaa8",
|
|
|
|
|
|
"device_token": "local-input-only",
|
|
|
|
|
|
"app_version": "0.1.0",
|
|
|
|
|
|
"android_version": "待设备填写"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
成功:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
2026-07-26 15:18:48 +08:00
|
|
|
|
"access_token": "opaque-token-returned-once",
|
2026-07-25 16:59:06 +08:00
|
|
|
|
"token_type": "Bearer",
|
|
|
|
|
|
"expires_in": 3600,
|
|
|
|
|
|
"user": {
|
|
|
|
|
|
"id": "d950db90-cf58-4f21-86fd-067e1a91a3a6",
|
|
|
|
|
|
"username": "buyer01",
|
|
|
|
|
|
"role": "BUYER"
|
|
|
|
|
|
},
|
|
|
|
|
|
"device": {
|
|
|
|
|
|
"id": "f0c3d438-1c44-4f80-b898-c645afe7eaa8",
|
|
|
|
|
|
"enabled": true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
密码和设备 token 不得出现在响应、日志或 execution event 中。
|
|
|
|
|
|
|
2026-07-26 15:18:48 +08:00
|
|
|
|
T-204 固定使用 256 bit 随机 opaque access token,数据库只保存 SHA-256,固定
|
|
|
|
|
|
1 小时过期。用户必须为有效 `BUYER`,设备必须预授权且启用;未绑定设备在首次成功
|
|
|
|
|
|
登录时原子绑定当前采购员,已绑定其他采购员时拒绝。T-204 不提供设备自助登记、
|
|
|
|
|
|
refresh 或 App logout;Android 安全存储接入属于 T-206。
|
|
|
|
|
|
|
|
|
|
|
|
App token 登录与管理登录使用独立限流 scope,同样为每个来源地址 5 分钟最多 10 次。
|
|
|
|
|
|
超限返回 `429`、`Retry-After` 和稳定错误码 `AUTH_RATE_LIMITED`,`retryable=true`。
|
|
|
|
|
|
|
2026-07-25 16:59:06 +08:00
|
|
|
|
## 资产
|
|
|
|
|
|
|
|
|
|
|
|
### `POST /api/v1/assets`
|
|
|
|
|
|
|
|
|
|
|
|
管理会话上传任务参考图片;App 也可用设备会话上传执行截图。请求必须带
|
|
|
|
|
|
`Idempotency-Key`。
|
|
|
|
|
|
|
|
|
|
|
|
表单字段:
|
|
|
|
|
|
|
2026-07-26 14:03:32 +08:00
|
|
|
|
- `file`:必填,MVP 允许可解码 JPEG/PNG/WebP;请求最多 20 MiB、最长边最多
|
|
|
|
|
|
10000 px、总像素最多 25 MP。
|
2026-07-25 16:59:06 +08:00
|
|
|
|
- `purpose`:`TASK_REFERENCE` 或 `EXECUTION_EVIDENCE`。
|
|
|
|
|
|
- `task_id`:证据图片必填;参考图创建时为空。
|
|
|
|
|
|
|
|
|
|
|
|
成功:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": "2bbc1bf2-30f7-497e-a52b-bcb4c61d57c3",
|
|
|
|
|
|
"media_type": "image/jpeg",
|
|
|
|
|
|
"size_bytes": 183245,
|
|
|
|
|
|
"sha256": "hex-value",
|
|
|
|
|
|
"created_at": "2026-07-25T08:30:00Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-26 14:03:32 +08:00
|
|
|
|
T-203 成功返回 `201`。使用相同 `Idempotency-Key` 和相同图片内容重试时返回同一
|
|
|
|
|
|
资产;同 key 不同内容返回 `409`。
|
|
|
|
|
|
|
|
|
|
|
|
`TASK_REFERENCE` 在后端统一白底合成、缩放到最长边不超过 2048 px,并以质量 90
|
|
|
|
|
|
编码为匿名 JPEG。响应的 `media_type`、`size_bytes` 和 `sha256` 都描述规范化结果,
|
|
|
|
|
|
不描述原始上传文件。T-203 尚不接受 `EXECUTION_EVIDENCE`。
|
|
|
|
|
|
|
2026-07-25 16:59:06 +08:00
|
|
|
|
### `GET /api/v1/assets/{asset_id}/content`
|
|
|
|
|
|
|
|
|
|
|
|
受鉴权的文件流。只能读取用户有权查看的任务资产;不返回服务端文件路径。
|
|
|
|
|
|
|
|
|
|
|
|
## 采购任务
|
|
|
|
|
|
|
|
|
|
|
|
### `POST /api/v1/tasks`
|
|
|
|
|
|
|
|
|
|
|
|
采购管理员或授权的外部管理系统创建任务。必须带 `Idempotency-Key`。
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"source_ref": "external-admin-task-10001",
|
|
|
|
|
|
"title": "黑色双肩包",
|
2026-07-26 14:03:32 +08:00
|
|
|
|
"sku": "BLACK-20L",
|
2026-07-25 16:59:06 +08:00
|
|
|
|
"description": "容量约20L,外观接近参考图",
|
|
|
|
|
|
"image_asset_id": "2bbc1bf2-30f7-497e-a52b-bcb4c61d57c3",
|
|
|
|
|
|
"quantity": 2,
|
|
|
|
|
|
"max_budget": "200.00"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
规则:
|
|
|
|
|
|
|
2026-07-26 14:03:32 +08:00
|
|
|
|
- `title`、`sku` 和 `image_asset_id` 必填,`description` 可为空。
|
|
|
|
|
|
- `title` 最多 120 个 Unicode 字符且不超过 2048 个 UTF-8 字节;`sku` 不超过
|
|
|
|
|
|
512 个 UTF-8 字节;`description` 不超过 8192 个 UTF-8 字节。
|
2026-07-25 16:59:06 +08:00
|
|
|
|
- `quantity` 是正整数。
|
2026-07-26 14:03:32 +08:00
|
|
|
|
- `max_budget` 可为空,否则为大于零、最多两位小数的 CNY 金额,表示当前任务全部
|
|
|
|
|
|
数量的最高商品总预算,不含尚无法确认的运费或优惠。
|
2026-07-25 16:59:06 +08:00
|
|
|
|
- 同一调用方的 `source_ref` 如填写必须唯一。
|
|
|
|
|
|
|
|
|
|
|
|
成功返回 `201`:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": "37c9c715-9b51-4ed5-984e-66dad2710c71",
|
|
|
|
|
|
"status": "PENDING",
|
|
|
|
|
|
"title": "黑色双肩包",
|
2026-07-26 14:03:32 +08:00
|
|
|
|
"sku": "BLACK-20L",
|
2026-07-25 16:59:06 +08:00
|
|
|
|
"quantity": 2,
|
|
|
|
|
|
"max_budget": "200.00",
|
|
|
|
|
|
"created_at": "2026-07-25T08:30:00Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `GET /api/v1/tasks`
|
|
|
|
|
|
|
2026-07-26 14:03:32 +08:00
|
|
|
|
管理端列表。可选参数:`q`、`status`、`created_from`、`created_to`、`limit`、
|
|
|
|
|
|
`cursor`。`q` 匹配任务 ID、`source_ref`、标题或 SKU;默认 `limit=20`,最大 100。
|
|
|
|
|
|
排序固定为 `created_at DESC, id DESC`,不透明 cursor 同时编码这两个字段。
|
2026-07-25 16:59:06 +08:00
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"items": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": "37c9c715-9b51-4ed5-984e-66dad2710c71",
|
|
|
|
|
|
"title": "黑色双肩包",
|
2026-07-26 14:03:32 +08:00
|
|
|
|
"sku": "BLACK-20L",
|
2026-07-25 16:59:06 +08:00
|
|
|
|
"status": "RUNNING",
|
|
|
|
|
|
"quantity": 2,
|
|
|
|
|
|
"device_name": "pdd-phone-01",
|
|
|
|
|
|
"updated_at": "2026-07-25T08:35:00Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"next_cursor": null
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `GET /api/v1/tasks/{task_id}`
|
|
|
|
|
|
|
|
|
|
|
|
管理会话返回完整任务、当前 execution、时间线、候选和资产元数据。App 只能读取
|
|
|
|
|
|
当前设备已领取的任务。响应必须同时包含:
|
|
|
|
|
|
|
|
|
|
|
|
- `original_requirement`:不可变原始输入。
|
|
|
|
|
|
- `derived_requirement`:模型输出,可能为空。
|
|
|
|
|
|
- `claim`:管理端可见设备和到期时间;claim token 永不返回。
|
|
|
|
|
|
- `execution`:step、outcome、错误、`order_submitted`。
|
|
|
|
|
|
- `events` 和 `assets`:有权限的摘要。
|
|
|
|
|
|
|
2026-07-26 15:18:48 +08:00
|
|
|
|
T-204 已实现的 `TASK_CREATED`、`TASK_CANCELED` 事件包含可空
|
|
|
|
|
|
`actor_user_id`;新管理操作写入真实 ADMIN 用户 ID,T-203 历史事件返回 `null`。
|
|
|
|
|
|
|
2026-07-25 16:59:06 +08:00
|
|
|
|
### `POST /api/v1/tasks/{task_id}/cancel`
|
|
|
|
|
|
|
|
|
|
|
|
管理端取消任务。`PENDING` 可立即取消;执行中只设置取消请求,App 在安全检查点确认
|
|
|
|
|
|
后进入 `CANCELED`。终态返回 `409`。
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"reason": "需求已撤销"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-26 16:16:36 +08:00
|
|
|
|
`PENDING/CLAIMED` 立即进入 `CANCELED`。`RUNNING/WAITING_CONFIRMATION` 只记录
|
|
|
|
|
|
取消请求并保持原状态;重复请求不重复写事件。App 的任务 heartbeat 会返回
|
|
|
|
|
|
`cancel_requested=true`,只有 App 在安全检查点调用 `cancel-ack` 后才进入
|
|
|
|
|
|
`CANCELED` 并结束 execution。
|
2026-07-26 14:03:32 +08:00
|
|
|
|
|
2026-07-28 12:25:26 +08:00
|
|
|
|
### `POST /api/v1/tasks/{task_id}/order-authorizations`
|
|
|
|
|
|
|
|
|
|
|
|
T-215 起由有效 ADMIN 会话对当前 `WAITING_CONFIRMATION` execution 创建一次性待投递
|
|
|
|
|
|
授权。Cookie API 请求必须带 `X-CSRF-Token` 和 `Idempotency-Key`;请求体见
|
|
|
|
|
|
[`T-215`](tasks/T-215.md)。关键规则:
|
|
|
|
|
|
|
|
|
|
|
|
- 顶层和每个 item 只引用 v7 `candidate_key`,不得用 ordinal 或 URL 选品。
|
|
|
|
|
|
- `items` 恰好覆盖当前 execution 全部 observation,只有顶层 key 对应项为
|
|
|
|
|
|
`ACCEPT`;所有项必须包含 T-208 schema v1 合法理由。
|
|
|
|
|
|
- `execution_id`、task content hash 和 expected task version 必须同时匹配;服务端
|
|
|
|
|
|
从任务/observation 读取 SKU、数量、候选规格、价格和身份指纹写入授权快照。
|
|
|
|
|
|
- 首次成功返回 `201` 和 `PENDING_DELIVERY` 授权;同 key 同 body 重放返回相同
|
|
|
|
|
|
授权并带 `replayed=true`,同 key 不同 body 返回 `409`。
|
|
|
|
|
|
- 未投递前改选必须引用当前 `supersedes_authorization_id`;旧授权变为
|
|
|
|
|
|
`SUPERSEDED`。已投递或不是最新授权时返回 `409`。
|
|
|
|
|
|
|
|
|
|
|
|
Admin `GET /api/v1/tasks/{task_id}` 同时返回当前及历史
|
|
|
|
|
|
`order_authorizations`。授权仅允许 T-216 设备命令领取,不代表浏览器可以直接操作
|
|
|
|
|
|
手机,也不授权付款。
|
|
|
|
|
|
|
2026-07-28 12:53:45 +08:00
|
|
|
|
### `POST /api/v1/tasks/{task_id}/commands/next`
|
|
|
|
|
|
|
|
|
|
|
|
T-216 起由原 BUYER/设备主动拉取当前 execution 的下单命令。请求带 bearer、
|
|
|
|
|
|
`X-Claim-Token`,body 提交 `device_id`、`execution_id` 和 `claim_generation`。
|
|
|
|
|
|
服务端校验有效 claim/租约、`WAITING_CONFIRMATION`、未取消和同一 active
|
|
|
|
|
|
authorization。没有命令返回 `204`;首次投递把授权置为 `DELIVERED`,之后对
|
|
|
|
|
|
`DELIVERED/ACKNOWLEDGED` 返回同一 schema v1 command。
|
|
|
|
|
|
|
|
|
|
|
|
命令只含 task/execution/content hash、原始 SKU/数量、candidate key、观测标题/
|
|
|
|
|
|
规格/价格和 T-214 四个指纹,不含第三方 URL。`observed_ordinal` 仅是重新定位提示。
|
|
|
|
|
|
`command_sha256` 固定 canonical payload,投递重试不得变化。
|
|
|
|
|
|
|
|
|
|
|
|
### `POST /api/v1/tasks/{task_id}/commands/{command_id}/ack`
|
|
|
|
|
|
|
|
|
|
|
|
App 严格校验并写入 Keystore-backed 加密状态后,使用 bearer、claim token、
|
|
|
|
|
|
`Idempotency-Key` 提交 execution、generation 和 `command_sha256`。成功把
|
|
|
|
|
|
`DELIVERED -> ACKNOWLEDGED`;相同 key/body 和同 command/hash 可安全重试,其他
|
|
|
|
|
|
command/hash/设备或无效租约返回冲突。ACK 只表示命令已可靠保存,不表示开始操作、
|
|
|
|
|
|
创建订单或付款。
|
|
|
|
|
|
|
2026-07-28 13:26:28 +08:00
|
|
|
|
### `POST /api/v1/tasks/{task_id}/order-dry-runs/start`
|
|
|
|
|
|
|
|
|
|
|
|
T-217 App 在加密保存 dry-run 意图后调用。请求带 BUYER bearer、claim token、
|
|
|
|
|
|
`Idempotency-Key`,body 固定 device、execution、generation、command id/hash。
|
|
|
|
|
|
服务端要求有效租约、任务仍为 `WAITING_CONFIRMATION`、未取消且授权为
|
|
|
|
|
|
`ACKNOWLEDGED`;事务内创建 PREPARING dry-run、把授权置为 `EXECUTING` 并写开始
|
|
|
|
|
|
事件。相同请求重放返回同一记录。
|
|
|
|
|
|
|
|
|
|
|
|
### `POST /api/v1/tasks/{task_id}/order-dry-runs/{command_id}/ready`
|
|
|
|
|
|
|
|
|
|
|
|
App 到达确认订单页并先加密保存 READY 后,提交当前 card/detail 指纹、规范化标题、
|
|
|
|
|
|
已选 SKU、数量、单价、商品总额和受控 evidence asset。服务端重新核对 command、
|
|
|
|
|
|
task/execution/claim、数量、预算和 evidence 归属后,把同一 dry-run 置为 READY 并写
|
|
|
|
|
|
事件;相同请求可重放。该响应不表示订单已提交,且不授权付款。
|
|
|
|
|
|
|
2026-07-28 16:56:17 +08:00
|
|
|
|
### `POST /api/v1/tasks/{task_id}/order-submissions/start`
|
|
|
|
|
|
|
|
|
|
|
|
T-218 App 在最终提交前调用。请求带 BUYER bearer、claim token、
|
|
|
|
|
|
`Idempotency-Key`,body 固定 device、execution、generation、authorization、
|
|
|
|
|
|
command id/hash、dry-run id/hash 和 App 重新核验的 SKU/数量/单价/总额。服务端要求
|
|
|
|
|
|
有效租约、未取消、authorization=`EXECUTING`、dry-run=`READY` 且所有快照完全一致。
|
|
|
|
|
|
|
|
|
|
|
|
成功事务内创建一个 `order_submission`、写 `ORDER_SUBMISSION_FENCED` 并使原授权
|
|
|
|
|
|
不可再次创建 submission。响应返回稳定 submission ID 和 `fenced_at`。同 key/body
|
|
|
|
|
|
重放返回相同记录;同 key 不同 body、第二个 submission 或非 READY 返回冲突。围栏
|
|
|
|
|
|
成功只表示 App 获得过一次点击机会;从此即使响应丢失或 App 退出也只能对账,不能
|
|
|
|
|
|
再次提交。
|
|
|
|
|
|
|
|
|
|
|
|
### `POST /api/v1/tasks/{task_id}/order-submissions/{submission_id}/reconcile`
|
|
|
|
|
|
|
|
|
|
|
|
App 唯一识别待付款订单并先上传受控 evidence 后,提交订单编号、平台显示下单时间、
|
|
|
|
|
|
标题、SKU、数量、金额、状态和 evidence asset/hash。服务端核对原 device/task/
|
|
|
|
|
|
execution/generation、submission、期望快照、证据归属和幂等键;成功将 submission
|
|
|
|
|
|
置为 `RECONCILED`、authorization 置为 `CONSUMED` 并记录
|
|
|
|
|
|
`order_submitted=true`。订单编号不得进入 URL、日志、事件 message 或模型字段。
|
2026-07-28 17:48:54 +08:00
|
|
|
|
围栏创建后允许原 user/device/generation/claim token 在租约到期后完成该 submission
|
|
|
|
|
|
的对账,但任务必须仍未取消且 execution 未结束;该例外不适用于 start,也不产生
|
|
|
|
|
|
新的提交资格。
|
2026-07-28 16:56:17 +08:00
|
|
|
|
|
|
|
|
|
|
### `POST /api/v1/tasks/{task_id}/order-submissions/{submission_id}/manual-review`
|
|
|
|
|
|
|
|
|
|
|
|
围栏后无法唯一回读订单时,App 用相同身份提交受限 reason code 和可选 evidence。
|
|
|
|
|
|
服务端将 submission 置为 `MANUAL_REVIEW` 并写审计事件,但不释放围栏、不允许生成
|
|
|
|
|
|
第二个 submission。后续只允许人员或同一设备重新对账,不提供“重试提交”接口。
|
|
|
|
|
|
|
|
|
|
|
|
三个接口都不接收支付方式、支付密码或付款结果,也不授权客户端点击付款控件。
|
|
|
|
|
|
|
2026-07-25 16:59:06 +08:00
|
|
|
|
## 设备与领取
|
|
|
|
|
|
|
|
|
|
|
|
### `POST /api/v1/devices/heartbeat`
|
|
|
|
|
|
|
|
|
|
|
|
App 空闲或运行时上报设备状态;运行时任务续租使用任务专用 heartbeat。
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"app_version": "0.1.0",
|
2026-07-26 16:16:36 +08:00
|
|
|
|
"android_version": "16",
|
2026-07-25 16:59:06 +08:00
|
|
|
|
"pdd_version": "device-observed-value",
|
|
|
|
|
|
"readiness": {
|
|
|
|
|
|
"accessibility_enabled": true,
|
|
|
|
|
|
"pdd_installed": true,
|
|
|
|
|
|
"active_task_id": null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-26 16:16:36 +08:00
|
|
|
|
`device_id` 可以省略;若提供,必须与 Bearer token 中的设备一致。服务端时间是唯一
|
|
|
|
|
|
租约时钟。响应返回服务端认定的 `active_task_id`、`client_state_matches`、
|
|
|
|
|
|
readiness 上报时间和 `server_time`。heartbeat 超过配置 TTL 或任一就绪位为 false
|
|
|
|
|
|
时,claim 返回 `409 DEVICE_NOT_READY`。
|
|
|
|
|
|
|
2026-07-25 16:59:06 +08:00
|
|
|
|
### `POST /api/v1/tasks/claim-next`
|
|
|
|
|
|
|
2026-07-26 16:16:36 +08:00
|
|
|
|
由用户点击触发,在单个 SQLite immediate transaction 中领取下一条任务。App 必须
|
|
|
|
|
|
在请求前生成并安全保存相互独立的 256 bit Raw URL `X-Claim-Token` 和
|
|
|
|
|
|
`Idempotency-Key`;网络结果不确定时复用同一组值。服务端只保存 token 的
|
|
|
|
|
|
SHA-256,响应和日志都不回显原 token 或 hash。
|
2026-07-25 16:59:06 +08:00
|
|
|
|
|
|
|
|
|
|
```json
|
2026-07-26 16:16:36 +08:00
|
|
|
|
{}
|
2026-07-25 16:59:06 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
有任务时:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"task": {
|
|
|
|
|
|
"id": "37c9c715-9b51-4ed5-984e-66dad2710c71",
|
|
|
|
|
|
"status": "CLAIMED",
|
|
|
|
|
|
"title": "黑色双肩包",
|
2026-07-26 14:03:32 +08:00
|
|
|
|
"sku": "BLACK-20L",
|
2026-07-25 16:59:06 +08:00
|
|
|
|
"description": "容量约20L,外观接近参考图",
|
|
|
|
|
|
"image_asset_id": "2bbc1bf2-30f7-497e-a52b-bcb4c61d57c3",
|
2026-07-27 09:43:04 +08:00
|
|
|
|
"reference_image_sha256": "64-char-lowercase-hex",
|
|
|
|
|
|
"task_content_sha256": "64-char-lowercase-hex",
|
2026-07-26 16:16:36 +08:00
|
|
|
|
"reference_image_url": "/api/v1/tasks/37c9c715-9b51-4ed5-984e-66dad2710c71/reference-image?claim_generation=1",
|
2026-07-25 16:59:06 +08:00
|
|
|
|
"quantity": 2,
|
2026-07-26 16:16:36 +08:00
|
|
|
|
"max_budget": "200.00",
|
|
|
|
|
|
"currency": "CNY",
|
|
|
|
|
|
"version": 2,
|
|
|
|
|
|
"claim_generation": 1,
|
|
|
|
|
|
"claim_issued_at": "2026-07-25T08:30:00Z",
|
|
|
|
|
|
"claim_expires_at": "2026-07-25T08:40:00Z"
|
2026-07-25 16:59:06 +08:00
|
|
|
|
},
|
2026-07-26 16:16:36 +08:00
|
|
|
|
"replayed": false,
|
|
|
|
|
|
"server_time": "2026-07-25T08:30:00Z"
|
2026-07-25 16:59:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-27 09:43:04 +08:00
|
|
|
|
`task_content_sha256` 是服务端对不可变任务内容生成的版本化摘要,客户端视为 opaque
|
|
|
|
|
|
并在候选和终态结果中原样回显;服务端拒绝与当前 execution 快照不一致的摘要。
|
|
|
|
|
|
App 下载参考图后必须独立校验 `reference_image_sha256`。
|
|
|
|
|
|
|
2026-07-26 16:16:36 +08:00
|
|
|
|
当前设备自己有已过期 `CLAIMED` 时优先回收该任务,避免设备唯一归属冲突;否则按
|
|
|
|
|
|
`created_at ASC, id ASC` 选择 `PENDING` 或已过期 `CLAIMED`。没有任务返回 `204`;
|
|
|
|
|
|
同 key 的无任务重放始终保持 `204`。同 key、同 token 的活跃 claim 重放返回同一
|
|
|
|
|
|
任务,即使状态已进入 `RUNNING/WAITING_CONFIRMATION`;请求不同返回
|
|
|
|
|
|
`409 IDEMPOTENCY_CONFLICT`;原 claim 已释放、取消或被其他领取回收时返回
|
|
|
|
|
|
`409 CLAIM_REPLAY_EXPIRED`。同一设备不能再领取第二条活跃任务。
|
|
|
|
|
|
|
|
|
|
|
|
### `GET /api/v1/tasks/{task_id}/reference-image`
|
|
|
|
|
|
|
|
|
|
|
|
claim 响应给出的受保护参考图地址。请求使用 BUYER Bearer token、
|
|
|
|
|
|
`X-Claim-Token` 和 URL 中的 `claim_generation`;只有当前用户、当前设备、匹配
|
|
|
|
|
|
generation/token 且租约未过期的活跃任务可以读取。成功返回匿名规范化 JPEG,
|
|
|
|
|
|
设置 `private, no-store`、`nosniff`、长度和 ETag,不返回存储路径。
|
2026-07-25 16:59:06 +08:00
|
|
|
|
|
|
|
|
|
|
### `POST /api/v1/tasks/{task_id}/start`
|
|
|
|
|
|
|
|
|
|
|
|
把当前设备持有的 `CLAIMED` 任务改为 `RUNNING` 并创建 execution。请求带
|
|
|
|
|
|
`X-Claim-Token` 和 `Idempotency-Key`。
|
|
|
|
|
|
|
2026-07-26 16:16:36 +08:00
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"claim_generation": 1,
|
|
|
|
|
|
"expected_version": 2
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
同 key、同请求重放返回同一 execution;错误用户/设备/token 返回 `403`,过期租约、
|
|
|
|
|
|
状态或版本冲突返回 `409`。成功响应包含更新后的 `task`、`execution`、`replayed`
|
2026-07-27 11:11:16 +08:00
|
|
|
|
和 `server_time`。默认 running lease 为 30 分钟(允许配置 5 至 120 分钟),
|
|
|
|
|
|
`execution.execution_expires_at` 是 App 可以离线继续自动化的上限,不是后台自动
|
|
|
|
|
|
重分配时间。
|
2026-07-26 16:16:36 +08:00
|
|
|
|
|
2026-07-25 16:59:06 +08:00
|
|
|
|
### `POST /api/v1/tasks/{task_id}/heartbeat`
|
|
|
|
|
|
|
|
|
|
|
|
运行时续租并返回是否请求取消:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
|
2026-07-26 16:16:36 +08:00
|
|
|
|
"claim_generation": 1,
|
|
|
|
|
|
"step": "SCAN_RESULTS"
|
2026-07-25 16:59:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
2026-07-26 16:16:36 +08:00
|
|
|
|
"task": {
|
|
|
|
|
|
"id": "37c9c715-9b51-4ed5-984e-66dad2710c71",
|
|
|
|
|
|
"status": "RUNNING",
|
|
|
|
|
|
"version": 4,
|
|
|
|
|
|
"claim_generation": 1,
|
2026-07-27 09:43:04 +08:00
|
|
|
|
"claim_expires_at": "2026-07-25T09:03:30Z"
|
2026-07-26 16:16:36 +08:00
|
|
|
|
},
|
|
|
|
|
|
"execution": {
|
|
|
|
|
|
"id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
|
|
|
|
|
|
"current_step": "SCAN_RESULTS",
|
2026-07-27 09:43:04 +08:00
|
|
|
|
"order_submitted": false,
|
|
|
|
|
|
"execution_expires_at": "2026-07-25T09:03:30Z"
|
2026-07-26 16:16:36 +08:00
|
|
|
|
},
|
|
|
|
|
|
"cancel_requested": false,
|
|
|
|
|
|
"server_time": "2026-07-25T08:33:30Z"
|
2026-07-25 16:59:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-26 16:16:36 +08:00
|
|
|
|
只接受 1 至 64 字节的大写 ASCII step。heartbeat 使用服务端 UTC 更新 execution、
|
2026-07-27 09:43:04 +08:00
|
|
|
|
设备最近在线时间、任务 version 和 `execution_expires_at`,不写高频任务事件。
|
|
|
|
|
|
App 每 30 秒 best-effort 调用;网络失败时可执行到上一次服务端截止时间。截止时间
|
2026-07-27 11:11:16 +08:00
|
|
|
|
到达后 App 持久化 `SAFE_STOPPED` 并停止外部动作,任务保持原非终态且绝不回到
|
|
|
|
|
|
领取队列。原设备重连后可以用同一 execution/claim heartbeat 同步状态;后端不延长
|
|
|
|
|
|
已经过期的授权,且此时只接受 `step=SAFE_STOPPED`。若响应包含取消请求,App 可以
|
|
|
|
|
|
继续调用 `cancel-ack`;没有取消时仍保持安全停止,不自动恢复采购。
|
2026-07-26 16:16:36 +08:00
|
|
|
|
|
2026-07-25 16:59:06 +08:00
|
|
|
|
### `POST /api/v1/tasks/{task_id}/release`
|
|
|
|
|
|
|
|
|
|
|
|
只允许尚未开始的 `CLAIMED` 任务释放回 `PENDING`。运行中使用取消/失败流程。
|
2026-07-26 16:16:36 +08:00
|
|
|
|
请求带 `X-Claim-Token`、`Idempotency-Key`,body 与 start 相同。成功后清除当前
|
|
|
|
|
|
用户、设备、token hash 和租约,但保留递增过的 `claim_generation` 供审计。
|
|
|
|
|
|
|
|
|
|
|
|
### `POST /api/v1/tasks/{task_id}/cancel-ack`
|
|
|
|
|
|
|
|
|
|
|
|
App 收到取消请求并在安全检查点停止后调用:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
|
|
|
|
|
|
"claim_generation": 1,
|
|
|
|
|
|
"expected_version": 5
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-27 09:43:04 +08:00
|
|
|
|
请求带 `X-Claim-Token` 和 `Idempotency-Key`。只有匹配的未结束 execution、设备
|
|
|
|
|
|
归属和已存在的管理取消请求可以确认;App 已在安全检查点停止时,即使离线授权刚
|
|
|
|
|
|
过期也允许原设备补交确认。成功把任务置为 `CANCELED`、结束 execution、清除 claim
|
|
|
|
|
|
秘密并追加带用户/设备 actor 的事件。同 key 重放不产生第二个事件。
|
2026-07-25 16:59:06 +08:00
|
|
|
|
|
2026-07-27 09:43:04 +08:00
|
|
|
|
## App 本地 AI 边界
|
2026-07-25 16:59:06 +08:00
|
|
|
|
|
2026-07-27 09:43:04 +08:00
|
|
|
|
MVP 后端不实现 `/api/v1/tasks/{task_id}/ai/*`,不保存 VLM 配置或 Key,也不代理
|
|
|
|
|
|
第三方模型。App 使用本机配置的 OpenAI 兼容 adapter 完成需求提取和候选评估;后台
|
|
|
|
|
|
任务 payload 不能携带或覆盖 provider、Base URL、model、prompt 或 API Key。
|
2026-07-27 09:22:03 +08:00
|
|
|
|
|
2026-07-27 09:43:04 +08:00
|
|
|
|
App 支持 `MANUAL_FIRST` 和 `AI_ASSISTED`。execution 结果必须记录实际模式;
|
|
|
|
|
|
`AI_ASSISTED` 还要记录 provider ID、model、prompt/schema version、reference/
|
|
|
|
|
|
candidate evidence SHA-256 和结构化模型判断。结果不得包含 Key、Authorization、
|
|
|
|
|
|
完整 endpoint、订单号、店铺名或供应商原始响应正文。
|
2026-07-27 09:22:03 +08:00
|
|
|
|
|
2026-07-27 09:43:04 +08:00
|
|
|
|
`sku`、`quantity` 和 `max_budget` 始终来自原任务,模型不能覆盖。App 按 ordinal
|
|
|
|
|
|
串行评估,每个 execution 最多一次需求提取、最多 5 次候选评估,并由本地确定性规则
|
|
|
|
|
|
产生建议。模型不能返回页面动作、建议 ordinal、人工确认状态或订单授权;低置信度、
|
|
|
|
|
|
无效 schema、证据不足或预算不确定时转人工。
|
2026-07-25 16:59:06 +08:00
|
|
|
|
|
|
|
|
|
|
## 执行事件与结果
|
|
|
|
|
|
|
2026-07-28 11:57:25 +08:00
|
|
|
|
App 使用加密 outbox 按“事件 -> evidence asset -> 候选 -> 人工 review -> 终态”
|
|
|
|
|
|
顺序提交。所有写接口
|
2026-07-27 09:43:04 +08:00
|
|
|
|
重新校验 BUYER/device/task/execution/claim 和幂等键。原设备可以在
|
|
|
|
|
|
`execution_expires_at` 后补报授权内已经产生的结果;后端记录
|
|
|
|
|
|
`received_after_execution_expiry=true`,但这不允许 App 在过期后继续自动化。
|
|
|
|
|
|
|
2026-07-25 16:59:06 +08:00
|
|
|
|
### `POST /api/v1/tasks/{task_id}/events`
|
|
|
|
|
|
|
|
|
|
|
|
批量追加事件,必须带 `Idempotency-Key`:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
|
2026-07-27 12:23:05 +08:00
|
|
|
|
"claim_generation": 1,
|
2026-07-25 16:59:06 +08:00
|
|
|
|
"events": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"event_id": "client-generated-uuid",
|
|
|
|
|
|
"step": "SEARCH",
|
|
|
|
|
|
"type": "STEP_COMPLETED",
|
|
|
|
|
|
"message": "已进入搜索结果页",
|
|
|
|
|
|
"occurred_at": "2026-07-25T08:34:00Z"
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`message` 不能包含凭证或完整个人敏感信息;同一 `event_id` 重放不重复插入。
|
|
|
|
|
|
|
2026-07-27 12:23:05 +08:00
|
|
|
|
### `POST /api/v1/tasks/{task_id}/evidence`
|
|
|
|
|
|
|
|
|
|
|
|
以原始 `image/jpeg`、`image/png` 或 `image/webp` body 上传一张受控截图;请求必须带
|
|
|
|
|
|
`Authorization: Bearer`、`X-Claim-Token`、`Idempotency-Key`、`X-Execution-ID` 和
|
|
|
|
|
|
`X-Claim-Generation`。服务端规范化存为 JPEG 并返回 asset ID、SHA-256、尺寸及
|
|
|
|
|
|
`received_after_execution_expiry`,不接受图片 URL,也不从第三方下载图片。
|
|
|
|
|
|
|
2026-07-27 09:43:04 +08:00
|
|
|
|
### `POST /api/v1/tasks/{task_id}/candidates`
|
|
|
|
|
|
|
2026-07-28 11:57:25 +08:00
|
|
|
|
批量保存当前 execution 实际检查的 `0..5` 个原始曝光候选,必须带
|
2026-07-27 18:35:59 +08:00
|
|
|
|
`Idempotency-Key`。正式参考图检索使用固定审计值 `PDD_IMAGE_SEARCH`,不把标题或
|
|
|
|
|
|
SKU 伪装成图片检索词:
|
2026-07-27 09:43:04 +08:00
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
|
2026-07-27 12:23:05 +08:00
|
|
|
|
"claim_generation": 1,
|
2026-07-27 09:43:04 +08:00
|
|
|
|
"task_content_sha256": "64-char-lowercase-hex",
|
|
|
|
|
|
"execution_mode": "AI_ASSISTED",
|
2026-07-27 18:35:59 +08:00
|
|
|
|
"search_query": "PDD_IMAGE_SEARCH",
|
2026-07-27 09:43:04 +08:00
|
|
|
|
"provenance": {
|
|
|
|
|
|
"provider_id": "device-configured-provider",
|
|
|
|
|
|
"model": "device-configured-model",
|
2026-07-27 18:35:59 +08:00
|
|
|
|
"prompt_version": "candidate-evaluation-v2",
|
|
|
|
|
|
"schema_version": 2
|
2026-07-27 09:43:04 +08:00
|
|
|
|
},
|
|
|
|
|
|
"candidates": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"ordinal": 1,
|
|
|
|
|
|
"title": "页面可见标题",
|
2026-07-27 18:35:59 +08:00
|
|
|
|
"sku_text": "BLACK-L",
|
2026-07-27 09:43:04 +08:00
|
|
|
|
"price": "189.00",
|
2026-07-28 12:21:55 +08:00
|
|
|
|
"product_url": "",
|
|
|
|
|
|
"image_url": "",
|
|
|
|
|
|
"card_signature": "64-char-lowercase-hex",
|
|
|
|
|
|
"detail_signature": "64-char-lowercase-hex",
|
|
|
|
|
|
"detail_evidence_sha256": "detail-asset-64-char-lowercase-hex",
|
|
|
|
|
|
"specification_evidence_sha256": "specification-asset-64-char-lowercase-hex",
|
|
|
|
|
|
"evidence_asset_ids": [
|
|
|
|
|
|
"7b733922-f90f-4bc4-a9ad-3e8ec4769122",
|
|
|
|
|
|
"4095ea37-eb4f-47c7-989b-adf060e45a32"
|
|
|
|
|
|
],
|
2026-07-27 09:43:04 +08:00
|
|
|
|
"evaluation": {
|
|
|
|
|
|
"decision": "REVIEW",
|
|
|
|
|
|
"score": 0.82,
|
2026-07-27 18:35:59 +08:00
|
|
|
|
"matched": ["颜色和尺码均有可见证据"],
|
|
|
|
|
|
"missing_or_uncertain": [],
|
2026-07-27 09:43:04 +08:00
|
|
|
|
"rejection_reasons": [],
|
2026-07-27 18:35:59 +08:00
|
|
|
|
"confidence": 0.78,
|
|
|
|
|
|
"hard_constraints": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"kind": "COLOR",
|
|
|
|
|
|
"expected": "BLACK",
|
|
|
|
|
|
"status": "MATCH",
|
|
|
|
|
|
"evidence": "候选页面显示黑色"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"kind": "SIZE",
|
|
|
|
|
|
"expected": "L",
|
|
|
|
|
|
"status": "MATCH",
|
|
|
|
|
|
"evidence": "候选页面显示 L 码"
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
2026-07-27 09:43:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"recommendation": {
|
|
|
|
|
|
"candidate_ordinal": 1,
|
|
|
|
|
|
"policy_version": "local-recommendation-v1",
|
|
|
|
|
|
"reasons": ["当前证据下匹配分最高"]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-27 18:35:59 +08:00
|
|
|
|
`MANUAL_FIRST` 时 `provenance` 和 `evaluation` 为空,但候选观察、搜索审计值和人工
|
2026-07-28 11:57:25 +08:00
|
|
|
|
结果仍可提交。`AI_ASSISTED` 为每个原始 observation 保存 evaluation;颜色/尺码状态
|
|
|
|
|
|
可以是 `MATCH/MISMATCH/UNKNOWN`,模型拒绝项也必须保留。schema v2 的
|
|
|
|
|
|
`recommendation` 只能指向颜色和尺码均为 `MATCH`、分数和置信度均不低于 `0.75`
|
|
|
|
|
|
且没有拒绝原因的原始 ordinal;没有满足项时只省略 recommendation,不能删除原始
|
|
|
|
|
|
候选。后端在同一事务内写入 search run、observation、model evaluation 和
|
2026-07-28 12:21:55 +08:00
|
|
|
|
recommendation,并保留旧 JSON 审计副本。每个非空候选必须按
|
|
|
|
|
|
`DETAIL`、`SPECIFICATION` 顺序绑定两个不同的 evidence asset ID,且两个声明哈希
|
|
|
|
|
|
必须分别等于后端保存的实际 asset 哈希;卡片和详情语义签名也必须是小写 SHA-256。
|
|
|
|
|
|
后端用版本化的 execution、原 ordinal、详情签名和规格证据哈希生成稳定的
|
|
|
|
|
|
execution-scoped `candidate_key`。Admin 任务详情在 observation 的 `identity` 中
|
|
|
|
|
|
返回该 key、四个指纹及 identity 版本;旧 v6 observation 可没有 identity。
|
|
|
|
|
|
`product_url` 和 `image_url` 只有设备实际取得可信 URL 时才提交,不能伪造;后端不
|
|
|
|
|
|
请求这些 URL,主要证据必须是已鉴权 asset。
|
2026-07-28 11:57:25 +08:00
|
|
|
|
|
|
|
|
|
|
### `POST /api/v1/tasks/{task_id}/human-reviews`
|
|
|
|
|
|
|
|
|
|
|
|
采购员确认候选后、提交终态前调用。请求使用设备 Bearer token、`X-Claim-Token`
|
|
|
|
|
|
和 `Idempotency-Key`:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
|
|
|
|
|
|
"claim_generation": 1,
|
|
|
|
|
|
"task_content_sha256": "64-char-lowercase-hex",
|
|
|
|
|
|
"reason_schema_version": 1,
|
|
|
|
|
|
"outcome": "CANDIDATE_ACCEPTED",
|
|
|
|
|
|
"selected_candidate_ordinal": 2,
|
|
|
|
|
|
"primary_reason_code": "SELECTED_BEST_MATCH",
|
|
|
|
|
|
"note": "",
|
|
|
|
|
|
"supersedes_review_id": null,
|
|
|
|
|
|
"items": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"candidate_ordinal": 1,
|
|
|
|
|
|
"label": "REJECT",
|
|
|
|
|
|
"primary_reason_code": "NOT_BEST_MATCH",
|
|
|
|
|
|
"reason_codes": ["NOT_BEST_MATCH"],
|
|
|
|
|
|
"note": ""
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"candidate_ordinal": 2,
|
|
|
|
|
|
"label": "ACCEPT",
|
|
|
|
|
|
"primary_reason_code": "SKU_MATCH",
|
|
|
|
|
|
"reason_codes": ["SKU_MATCH"],
|
|
|
|
|
|
"note": ""
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
非空 review 必须恰好覆盖本次所有 observation;接受时只有所选 ordinal 为
|
|
|
|
|
|
`ACCEPT`,其余全部为 `REJECT`。零候选只允许 `NO_MATCH` 或
|
|
|
|
|
|
`MANUAL_REQUIRED` 且 items 为空。理由使用 T-208 版本 1 allowlist;任务没有预算时
|
|
|
|
|
|
禁止价格类理由,`OTHER` 必须带 4-200 字备注。同一幂等键重放返回原 review;
|
|
|
|
|
|
修订必须通过 `supersedes_review_id` 引用当前最新版本,服务端追加版本并保留历史。
|
2026-07-27 09:43:04 +08:00
|
|
|
|
|
2026-07-25 16:59:06 +08:00
|
|
|
|
### `POST /api/v1/tasks/{task_id}/complete`
|
|
|
|
|
|
|
|
|
|
|
|
人员完成确认后调用,必须带 `Idempotency-Key`:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
|
2026-07-27 12:23:05 +08:00
|
|
|
|
"claim_generation": 1,
|
2026-07-27 09:43:04 +08:00
|
|
|
|
"task_content_sha256": "64-char-lowercase-hex",
|
|
|
|
|
|
"execution_mode": "AI_ASSISTED",
|
2026-07-25 16:59:06 +08:00
|
|
|
|
"outcome": "CANDIDATE_ACCEPTED",
|
|
|
|
|
|
"operator_reason": "款式和预算符合验证要求",
|
|
|
|
|
|
"candidate": {
|
2026-07-27 09:43:04 +08:00
|
|
|
|
"ordinal": 1,
|
2026-07-25 16:59:06 +08:00
|
|
|
|
"title": "页面可见标题",
|
2026-07-28 12:21:55 +08:00
|
|
|
|
"sku_text": "BLACK-L",
|
2026-07-25 16:59:06 +08:00
|
|
|
|
"price": "189.00",
|
2026-07-28 12:21:55 +08:00
|
|
|
|
"product_url": "",
|
|
|
|
|
|
"image_url": "",
|
|
|
|
|
|
"card_signature": "64-char-lowercase-hex",
|
|
|
|
|
|
"detail_signature": "64-char-lowercase-hex",
|
|
|
|
|
|
"detail_evidence_sha256": "detail-asset-64-char-lowercase-hex",
|
|
|
|
|
|
"specification_evidence_sha256": "specification-asset-64-char-lowercase-hex",
|
|
|
|
|
|
"evidence_asset_ids": [
|
|
|
|
|
|
"7b733922-f90f-4bc4-a9ad-3e8ec4769122",
|
|
|
|
|
|
"4095ea37-eb4f-47c7-989b-adf060e45a32"
|
|
|
|
|
|
],
|
|
|
|
|
|
"evaluation": null
|
2026-07-25 16:59:06 +08:00
|
|
|
|
},
|
|
|
|
|
|
"order_submitted": false
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`outcome` 允许:
|
|
|
|
|
|
|
|
|
|
|
|
- `CANDIDATE_ACCEPTED`
|
|
|
|
|
|
- `CANDIDATE_REJECTED`
|
|
|
|
|
|
- `NO_MATCH`
|
|
|
|
|
|
- `MANUAL_REQUIRED`
|
|
|
|
|
|
|
|
|
|
|
|
后端对 MVP 强制 `order_submitted=false`,成功后任务进入 `SUCCEEDED`;这里的成功表示
|
|
|
|
|
|
验证工作流正常结束,业务结果由 `outcome` 表达。
|
|
|
|
|
|
|
2026-07-26 20:16:42 +08:00
|
|
|
|
T-207 第一版要求 `operator_reason` 为人员输入的简短审计说明,但不把它当作可训练
|
|
|
|
|
|
标签。T-208 在第一版链路跑通后扩展为版本化 `human_review` 合约,至少包含
|
|
|
|
|
|
`reason_schema_version`、可空选择候选、逐候选 `ACCEPT/REJECT`、主要理由码、
|
|
|
|
|
|
附加理由码和受限备注。改选必须同时提交原推荐项拒绝理由与替代项选择理由;全部
|
|
|
|
|
|
无匹配必须覆盖每个曝光候选。具体 endpoint 和 schema 在领取 T-208 时冻结。
|
|
|
|
|
|
|
|
|
|
|
|
模型评估理由、确定性推荐理由和 `human_review` 分开保存。服务端不接受客户端把
|
|
|
|
|
|
模型理由标记为已由人员确认;第三方商品/图片 URL 只作为受限观测字段,不触发后端
|
|
|
|
|
|
下载,主要证据仍通过鉴权 asset API 上传。
|
|
|
|
|
|
|
2026-07-25 16:59:06 +08:00
|
|
|
|
### `POST /api/v1/tasks/{task_id}/fail`
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
|
2026-07-27 12:23:05 +08:00
|
|
|
|
"claim_generation": 1,
|
2026-07-25 16:59:06 +08:00
|
|
|
|
"error": {
|
|
|
|
|
|
"code": "PDD_RISK_CONTROL",
|
|
|
|
|
|
"message": "检测到平台风险提示,已停止自动化",
|
|
|
|
|
|
"step": "SCAN_RESULTS",
|
|
|
|
|
|
"retryable": false
|
|
|
|
|
|
},
|
|
|
|
|
|
"evidence_asset_ids": ["7b733922-f90f-4bc4-a9ad-3e8ec4769122"]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
后端只接受文档登记的错误码族和合法状态迁移。
|
|
|
|
|
|
|
|
|
|
|
|
## 待实现前固定
|
|
|
|
|
|
|
|
|
|
|
|
- 上传大小、像素和保留期限的具体数值。
|
|
|
|
|
|
- VLM `confidence` 阈值、模型和提示词版本记录格式。
|
|
|
|
|
|
- 外部管理后台的服务账号认证方式和调用频率。
|