diff --git a/README.md b/README.md index 18f245b..48791de 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,14 @@ ```text 后台创建任务 -> Android App 手动领取 - -> VLM 解析图片与文字 + -> App 本地 VLM 解析图片与文字 -> 拼多多搜索和候选比较 -> 停在人工确认位置 -> 回传结果、截图和失败原因 ``` -MVP 不自动提交订单、不支付、不绕过验证码或平台风控。 +管理后端只负责任务控制和结果审计,不保存或代理 VLM;App 使用有限离线授权和本地 +加密 outbox。MVP 不自动提交订单、不支付、不绕过验证码或平台风控。 ## 目标结构 diff --git a/docs/00-ai-start-here.md b/docs/00-ai-start-here.md index 007058d..64ed819 100644 --- a/docs/00-ai-start-here.md +++ b/docs/00-ai-start-here.md @@ -58,9 +58,9 @@ `HttpTaskSource`、前台服务和已确认的任务页面接到设备 API。 候选优化数据集已经登记为 T-208;不得跳过 T-206/T-207 的第一版端到端闭环,提前 建设报表、训练管线或外部商品抓取。 -生产采购 App 只连接肉包后端;T-207 由 ADMIN 在管理 Web 配置 OpenAI 兼容 VLM, -后端代理调用且供应商 API Key 永不下发手机。Roubao 原有端上 provider 设置不能用于 -后端采购任务。 +手机从管理后端领取任务并回传结果,VLM、拼多多自动化和人工确认在 App 本地完成。 +T-206 增加有限离线执行;T-207 复用 Roubao 端上 OpenAI 兼容适配器并加密本地 Key。 +管理后端不保存/代理 VLM,后台任务不能覆盖手机 provider 配置。 严格按以下顺序推进: diff --git a/docs/02-requirements.md b/docs/02-requirements.md index 6745139..9cc545f 100644 --- a/docs/02-requirements.md +++ b/docs/02-requirements.md @@ -32,22 +32,22 @@ | F-005 | 拼多多搜索与候选判断 | App 搜索并检查少量结果,得到一个候选或明确无匹配。 | P0 | US-004 | | F-006 | 人工确认停止点 | 自动化停在候选/订单确认位置,采购员确认结果或拒绝候选。 | P0 | US-005 | | F-007 | 结果与异常回传 | 管理员和采购员看到成功、失败、取消及可恢复建议。 | P0 | US-002、US-006 | -| F-009 | 集中配置 VLM 服务 | 管理员配置 OpenAI 兼容服务,App 经后端使用且不持有供应商密钥。 | P0 | US-009 | +| F-009 | 手机独立执行与 VLM | App 本地完成模型判断和自动化,后端只控制任务并接收结果。 | P0 | US-009 | -### F-009 集中配置 VLM 服务 +### F-009 手机独立执行与 VLM -1. 正式采购链路固定为 `Android App -> 肉包后端 -> VLM Provider`。采购执行员不能 - 在 App 选择或修改 provider、Base URL、model、prompt、超时、重试或 API Key。 -2. ADMIN 在管理 Web 配置 OpenAI 兼容服务;第一版只支持 - `/v1/chat/completions` 多模态协议和一个供新 execution 使用的默认 provider。 -3. API Key 只写不读,使用后端环境 master key 加密落库;明文不得出现在 HTML、 - JSON、日志、事件、崩溃报告、Android 存储或 Git。 -4. 配置更新创建版本。execution 首次调用时绑定 provider/model/config/prompt - 版本,后续调用不能因管理员修改默认配置而漂移。 -5. 远程服务必须使用 HTTPS;后端阻断重定向、私网/本机/链路本地/metadata 目标和 - DNS rebinding。Debug 本机无 Key mock 与生产配置隔离。 -6. App 只能看到肉包后端返回的 AI 可用/不可用状态和结构化结果,不能获得供应商 - 地址、密钥或原始响应正文。 +1. 正式采购链路为:App 从管理后端领取完整任务,直接调用本机配置的 VLM、操作 + 拼多多和人工确认,再向管理后端提交结构化结果和证据。 +2. 管理后端不保存、代理或下发 provider、Base URL、model、prompt 和 API Key; + 后台任务也不能覆盖 App 的本地模型设置。 +3. App 支持 `MANUAL_FIRST` 和 `AI_ASSISTED`。没有 VLM 时仍可直接搜索并由人员判断; + 发生模式切换必须明确显示和记录,不能静默降级。 +4. provider Key 使用 Android Keystore 包装的加密存储。每台设备使用独立、可撤销、 + 有限额度的 Key;普通日志、结果和崩溃报告不得包含秘密。 +5. start 后默认获得 30 分钟有限离线执行授权,成功 heartbeat 可续期。后端短时不可用 + 不阻止授权内执行;授权到期必须安全停止,RUNNING 任务不得自动分给其他设备。 +6. 回传结果包含模式、provider/model/prompt/schema、任务和证据哈希、候选、推荐、 + 人工理由及设备版本,但不包含 Key、Authorization、完整 endpoint 或供应商正文。 ## 四、后续迭代 @@ -119,6 +119,11 @@ T-206/T-207 先跑通领取、执行、候选/证据和最小结果回传;T-20 过期后不得自动回到队列,避免失联设备与新设备重复执行。 17. 管理取消 `PENDING/CLAIMED` 可立即终态;执行中只设置取消请求,必须等 App 在 安全检查点确认停止后才能进入 `CANCELED`。 +18. start 后默认运行授权为 30 分钟,App 每 30 秒 best-effort heartbeat;断网时可以 + 执行到服务端下发的截止时间,到期后必须安全停止。离线期间管理取消不能保证即时 + 生效,恢复连接后必须在继续动作前同步。 +19. App 本地 VLM 配置不能来自任务 payload。后端不代理模型;端上 Key 必须加密存储, + 模型不可用时允许显式切换 `MANUAL_FIRST`,不能阻塞人工完成任务。 ## 六、第一层本地样本约定 @@ -176,6 +181,8 @@ T-004 已固定首版规则:推荐私有目录为被 Git 忽略的 `private-fi 打开任务后证据仍可查看。 - US-007/IX-001/IX-004:管理员可登录和退出,采购员可用预授权设备取得 1 小时 Bearer token;匿名、角色错误、禁用、过期、篡改和跨端凭证替代均被拒绝。 +- F-009/US-009/IX-010:后台任务不能覆盖手机 VLM 配置;断开管理后端超过 90 秒后, + App 仍可在服务端下发的有限授权内执行,授权到期安全停止且任务不被重复分配。 ### 试验验收 @@ -202,6 +209,8 @@ T-004 已固定首版规则:推荐私有目录为被 Git 忽略的 `private-fi | 下单边界 | MVP 停在候选或订单确认页,不提交订单、不支付。 | | 账号边界 | 验证版为 ADMIN 会话 + BUYER/预授权设备联合身份;完整人员 RBAC 后置。 | | 第一层任务输入 | 从本机私有蝦皮订单文件生成测试任务,不先建设 Go-Gin。 | +| 执行边界 | 手机本地完成 VLM、拼多多自动化和人工确认;后端负责身份、任务控制和结果审计。 | +| 离线边界 | start 默认授权 30 分钟;后端断线可继续到截止时间,过期安全停止且不自动重分配。 | ## 九、待确认与风险 diff --git a/docs/03-tech-stack.md b/docs/03-tech-stack.md index 2ed79dd..c01d54d 100644 --- a/docs/03-tech-stack.md +++ b/docs/03-tech-stack.md @@ -25,7 +25,8 @@ | 图片/截图 | 后端受控本地文件目录 + `golang.org/x/image` v0.28.0 | 已验证 | JPEG/PNG/WebP 真解码后白底缩放并编码为 JPEG;数据库只存元数据和随机相对键。 | | 管理鉴权 | bcrypt + 8 小时 opaque 服务端会话 Cookie | T-204 已验证 | `authctl` 预置 ADMIN;数据库只存密码 hash 与 session SHA-256,完整 RBAC 为 V2。 | | App 鉴权 | BUYER 密码 + 预授权设备 secret + 1 小时 opaque token | T-204 已验证 | 首次原子绑定空闲设备;数据库只存 token SHA-256,不提供自助登记/refresh。 | -| VLM 接入 | 应用内统一适配器,优先兼容 OpenAI 风格多模态接口 | 需求提取与候选评估已实现,供应商待定 | T-103/T-104 使用严格 JSON Schema、单候选单次调用和 2048 px 图片上限;GUI-Owl/MAI-UI 动作模型不具备需求提取能力。 | +| VLM 接入 | Android 应用内统一适配器,优先兼容 OpenAI 风格多模态接口 | 需求提取与候选评估已实现,供应商待定 | 手机直连 provider;后端不代理模型。T-207 把 Key 迁移到 Keystore-backed 加密存储,并回传非秘密 provenance。 | +| 离线执行 | 服务端运行授权 + Android 加密本地状态/outbox | T-206 待实现 | 默认 30 分钟、5-120 分钟可配;30 秒 best-effort heartbeat,过期安全停止且 RUNNING 不自动重分配。 | | 通知 | MVP 不使用推送 | 已定 | 点击“获取任务”调用原子 claim API;V2 再评估厂商推送/WebSocket。 | | 后端测试 | 标准库 `testing` + `httptest` | T-205 已验证 | 当前 192 个测试覆盖配置、迁移、图片限制、任务事务、鉴权隔离、设备就绪、原子领取、幂等重放、租约状态机、取消确认、跨连接与真实 TCP 并发。 | | Android 测试 | Gradle `test` + `kotlinx-coroutines-test` 1.7.3 + 真实设备 smoke | Phase 1 探针已验证 | 166 次测试覆盖 runner、动态页面分类、受控证据、VLM schema、人工确认策略与隐私;OnePlus PKG110 上完成私有 fixture + 本机 mock 的需求提取和 5 候选评估 smoke。 | @@ -129,7 +130,8 @@ T-203 已建立 `domain/usecase/repository/sqlite` 和 `transport/webui`,handl 执行 SQL,Web 与 JSON API 复用同一 usecase。 T-205 已在该分层上增加独立 `LifecycleService` 与 SQLite immediate transaction, 固定 client-generated claim token、设备 readiness、CLAIMED/运行租约、execution、 -安全取消和 task-scoped 参考图授权;Android HTTP TaskSource 仍由 T-206 接入。 +安全取消和 task-scoped 参考图授权;当前 90 秒运行租约将在 T-206 扩展为默认 +30 分钟有限离线授权,Android HTTP TaskSource 同时接入。 ## 构建与运行命令 diff --git a/docs/04-architecture.md b/docs/04-architecture.md index df83894..b9a444b 100644 --- a/docs/04-architecture.md +++ b/docs/04-architecture.md @@ -11,12 +11,12 @@ 管理 Web(服务端渲染) | v -统一 Backend API ---------------------+ - | | - +--> SQLite / 文件存储 +--> VLM Provider +统一 Backend API + | + +--> SQLite / 文件存储 ^ | -Android 采购 App +Android 采购 App --------------------> VLM Provider | +--> AccessibilityService | @@ -25,9 +25,10 @@ Android 采购 App ``` - 管理 Web 与 API 同属 `backend-api/`,但页面层不能直接访问数据库。 -- Android App 位于 `android-buyer/`,通过 API 领取任务、调用 AI 能力和回传证据。 +- Android App 位于 `android-buyer/`,通过 API 领取任务和回传证据,在本地调用 AI。 - 拼多多是第三方受控边界,只能由 Android 设备在已登录会话中操作。 -- VLM 密钥的正式路径保留在后端;技术探针如需端上临时调用,只能使用本地忽略配置。 +- VLM 配置和 Key 位于手机,Key 使用 Android Keystore 包装的加密存储;管理后端不 + 保存、下发或代理模型调用。 ## 二、模块职责 @@ -41,7 +42,7 @@ internal/transport/http/ # Gin 路由、中间件、页面和请求/响应 DT internal/usecase/ # 创建、领取、状态迁移和结果归档 internal/domain/ # 实体、状态机、权限和确定性约束 internal/repository/sqlite/ # database/sql 仓储实现 -internal/platform/ # 配置、日志、文件存储和 VLM 适配器 +internal/platform/ # 配置、日志和文件存储 ``` 职责: @@ -50,7 +51,6 @@ internal/platform/ # 配置、日志、文件存储和 VLM 适配器 - 创建任务并保存原始输入,不让模型结果覆盖原始事实。 - 原子领取下一条任务,签发有期限的 claim lease。 - 校验任务状态迁移、幂等键和设备归属。 -- 代理 VLM 调用并把供应商响应转换为领域结构。 - 保存候选、事件、截图元数据和结果。 - 为管理 Web 提供任务列表、详情和取消能力。 @@ -88,7 +88,7 @@ taskclient/ # 后端 API、认证、幂等和重试 tasksource/ # 统一任务源;本地探针与后端 API 实现 workflow/ # ProcurementWorkflow 状态机 automation/ # Accessibility 节点、截图、点击、输入、滑动 -ai/ # 结构化 AI 请求/响应,不含供应商业务类型 +ai/ # 结构化 AI、OpenAI 兼容适配器和本地安全配置 evidence/ # 截图、步骤日志、脱敏和上传 ``` @@ -97,8 +97,10 @@ evidence/ # 截图、步骤日志、脱敏和上传 - 检查无障碍、拼多多安装/登录可用性、网络和当前任务状态。 - 由用户点击后领取任务;一台设备一次只运行一条。 - 执行有界步骤:解析 -> 搜索 -> 浏览不超过 5 个候选 -> 判断 -> 停止确认。 +- 直接调用用户在本机明确配置的 VLM;没有可用模型时允许显式人工优先模式。 - 显示当前步骤、停止原因和人工接管入口。 -- 用前台服务承载执行中任务,进程重启后从服务端状态恢复或安全失败。 +- 用前台服务承载执行中任务,以服务端授权截止时间支持有限离线;进程重启后从加密 + 本地状态与服务端核对恢复或安全失败。 - 回传步骤事件、候选、截图和最终结果。 不得: @@ -115,22 +117,22 @@ evidence/ # 截图、步骤日志、脱敏和上传 1. **需求提取**:参考图 + 标题 + SKU -> 搜索词、类目、属性、置信度、警告。 2. **候选评估**:候选截图/文本 + 原始约束 -> 匹配项、缺失项、拒绝原因、建议分。 -正式数据流固定为 `Android App -> 肉包后端 -> VLM Provider`。生产采购 App 不选择 -provider、不保存供应商 API Key,也不直连模型地址;它只调用 task-scoped AI API。 -ADMIN 在管理 Web 维护一个供新 execution 使用的默认 OpenAI 兼容 provider 版本。 -第一版只支持 `/v1/chat/completions` 多模态协议。 +正式数据流固定为 `Android App -> VLM Provider`。管理后端只下发原始任务并接收 +结构化结果,不保存/下发 provider 配置或 Key,也不实现 `/tasks/{id}/ai/*` 代理。 +后台任务不能携带或覆盖本机 provider、Base URL、model、prompt 或 API Key。 -provider 配置采用追加版本:保存名称、HTTPS Base URL、model、timeout、retry 上限、 -启用状态和非秘密审计字段。API Key 使用环境提供的 256 bit master key,通过 AEAD -随机 nonce 加密;AAD 绑定 provider/version。读取接口只返回 `has_secret`,永不返回 -明文或密文。execution 在第一次 AI 调用时绑定 config/provider/model/prompt version, -后续调用不随默认配置变化。master key 缺失或解密失败时真实调用安全失败,但管理任务、 -领取和非 AI 功能继续可用。 +App 支持两个显式模式: -后端 provider transport 禁止重定向,并在解析和连接时阻断 loopback、私网、链路 -本地、组播、云 metadata 和 DNS rebinding;生产只允许 HTTPS。无 Key 的 loopback -HTTP 只属于显式 Debug mock,不能从生产管理 Web 创建。连接测试只使用内置脱敏文字 -和测试图片,不读取订单或任务资产。 +- `MANUAL_FIRST`:从原始标题/SKU 产生有界搜索词,由人员判断候选,不要求 VLM。 +- `AI_ASSISTED`:使用 App 本地配置的 OpenAI 兼容 provider 做需求提取和候选评估。 + +模式在 execution 开始时固定并写入结果;AI 失败后只能由人员明确切换,不能静默降级。 +App 同时固定 provider ID、model、prompt/schema version 和证据 SHA-256,作为非秘密 +provenance 回传。Key、Authorization、完整 endpoint 和供应商原始响应正文不回传。 + +provider Key 从旧设置迁移到 Android Keystore 包装的加密存储;迁移或解密失败时 +拒绝真实调用。每台设备使用独立、可撤销、有限额度的 Key。Keystore 只降低静态泄露 +风险,不能宣称可抵抗已 Root 或完全受控设备。 模型输出是不可信建议,必须通过 schema 和确定性校验: @@ -154,19 +156,18 @@ T-103 的模型响应只允许 输出并转人工。最终领域结果再由确定性代码补回原 SKU、数量、空预算、人工复核原因和 `provider/model/prompt_version/reference_image_sha256`。 -技术探针每次点击最多发出一次 VLM 请求,不沿用通用 Agent 的重试循环。T-207 后端 -默认不重试,管理员最多允许一次仅限确定未收到响应的临时网络失败;schema、4xx 和 -安全错误绝不重试。该结构化 HTTP 客户端关闭连接自动重试和 HTTP/HTTPS 重定向, -整体调用超时为 75 秒;协程取消 +技术探针和后台 execution 的每个逻辑步骤最多发出一次 VLM 请求,不沿用通用 Agent +的重试循环。该结构化 HTTP 客户端关闭连接自动重试和 HTTP/HTTPS 重定向,整体调用 +超时为 75 秒;协程取消 会取消底层 HTTP call,成功响应正文上限为 64 KiB,非成功响应不读取正文。 -远程 provider 只允许 HTTPS;HTTP 仅允许 Debug 下无 API Key 的 `localhost`、 +Release 远程 provider 只允许 HTTPS;HTTP 仅允许 Debug 下无 API Key 的 `localhost`、 `127.0.0.1` 或 `::1` 本机 mock。端点不得包含 userinfo、query 或 fragment。标题 和 SKU 在进入 prompt 前分别限制为 2048 和 512 个 UTF-8 字节。JPEG 在调用前复核媒体类型、20 MiB 上限、魔数和 SHA-256,按声明长度一次分配并精确读取,Android 解码后最长边限制为 -2048 px。请求和响应正文、Base64、API Key 不写普通日志;加密密钥存储不可用且存在 -密钥时拒绝调用。 +2048 px。请求和响应正文、Base64、API Key 不写普通日志;Keystore-backed 密钥存储 +不可用且存在 Key 时拒绝调用。 ## 三、核心数据流 @@ -274,11 +275,12 @@ T-102/T-104 在搜索结果后追加一个有界候选步骤: 管理员创建 PENDING 任务 -> 采购员在 App 点击“获取任务” -> 后端事务内选取并置为 CLAIMED - -> App 确认后置为 RUNNING - -> 定期 heartbeat 续租并上报步骤 - -> 需求提取和拼多多自动化 + -> App 确认后置为 RUNNING,取得有限离线授权 + -> best-effort heartbeat 同步步骤和取消 + -> App 本地需求提取和拼多多自动化 -> WAITING_CONFIRMATION -> 采购员接受/拒绝候选 + -> 加密 outbox 幂等回传结果和证据 -> SUCCEEDED 或 FAILED/CANCELED -> 管理端展示结果 ``` @@ -312,16 +314,19 @@ CLAIMED/RUNNING/WAITING_CONFIRMATION 规则: - `SUCCEEDED` 是验证结果成功,不代表已下单;`order_submitted=false`。 -- `CLAIMED` 默认租约为 10 分钟;`RUNNING/WAITING_CONFIRMATION` 默认租约为 - 90 秒,App 计划每 30 秒 heartbeat 续租。三个时长均由有上下界的环境变量配置。 +- `CLAIMED` 默认租约为 10 分钟;T-206 将 `RUNNING/WAITING_CONFIRMATION` 默认 + 授权从当前 90 秒扩展为 30 分钟,允许范围 5 至 120 分钟。App 每 30 秒 + best-effort heartbeat,成功后按服务端时间滑动续期。 - 只有当前设备和有效 claim token 能启动、续租、上报或结束任务。 - 终态不可回退;重试创建新的 execution attempt,不篡改历史证据。 - App 在 claim 前生成并安全保存 256 bit Raw URL token;后端只存 SHA-256, token 与 `Idempotency-Key` 相互独立。 - 过期 `CLAIMED` 可以原子回收;过期 `RUNNING/WAITING_CONFIRMATION` 保持原状态, - 不自动回队列。 + 不自动回队列。App 到达服务端授权截止时间必须安全停止,但原设备可以在重连后 + 补报已产生的终态结果并留下过期补报审计标志。 - 管理取消 `PENDING/CLAIMED` 立即终态;执行中只设置停止请求,App 在下一个安全 - 检查点停止并调用 `cancel-ack` 后才进入 `CANCELED`。 + 检查点停止并调用 `cancel-ack` 后才进入 `CANCELED`。离线期间不能保证即时取消, + App 恢复连接后必须先同步取消状态再继续页面动作。 ### 4.2 Android 工作流状态 diff --git a/docs/05-coding-rules.md b/docs/05-coding-rules.md index 86f2949..1f157c9 100644 --- a/docs/05-coding-rules.md +++ b/docs/05-coding-rules.md @@ -116,13 +116,14 @@ ## 7. 安全与隐私 -- 通用密钥只来自环境变量或被忽略的本地配置。T-207 的 provider API Key 是唯一 - 例外:只能由 ADMIN write-only 写入,使用环境 master key + AEAD 随机 nonce - 加密落库;读取接口、HTML、日志、事件、错误和 App 永不返回明文或密文。 -- 生产采购 App 只调用肉包后端,不保存或直连 VLM provider。Debug 本机 mock 必须 - 位于 Debug source set,配置被 Git 忽略且不得进入 Release APK。 -- provider HTTP transport 必须禁用重定向,并在解析和连接阶段阻断私网、本机、 - 链路本地、组播、metadata 与 DNS rebinding;生产远程目标只允许 HTTPS。 +- 后端密钥只来自环境变量或被忽略的本地配置;管理后端不保存、下发或代理 VLM + provider Key。 +- App provider Key 使用 Android Keystore 包装的加密存储;每台采购设备使用独立、 + 可撤销、有限额度的 Key。Key、Authorization 和完整敏感 endpoint 不得进入任务、 + 结果、日志、崩溃报告或普通 SharedPreferences。 +- App provider HTTP client 必须禁用自动重试和重定向;Release 远程目标只允许 + HTTPS,HTTP 仅允许 Debug 下无 Key 的 loopback mock。后台任务不能覆盖端点。 +- 管理后端不得根据 App 提交的 provider 或商品 URL 发起第三方网络请求。 - 真实蝦皮订单文本、参考图及其规范化生成物只能位于仓库外或 `.local/`、 `private-fixtures/` 等被忽略目录;测试使用脱敏 fixture。 - 普通日志不得输出完整蝦皮订单号、店铺名或本机原图绝对路径。 diff --git a/docs/06-tasks.md b/docs/06-tasks.md index 1369323..e73ee03 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -38,8 +38,8 @@ | T-203 | 实现任务创建 API 与管理 Web | T-201、T-202 | 合法任务进入 PENDING;校验、列表和详情状态可见 | | T-204 | 实现用户/设备最小鉴权 | T-201、T-202 | 管理会话和设备令牌隔离;未授权访问被拒绝;无明文凭证 | | T-205 | 实现原子 claim、租约和状态机 | T-203、T-204 | 并发领取不重复;非法迁移、过期租约和错误设备被拒绝 | -| T-206 | App 接入手动领取和执行进度 | T-202、T-205 | 用户点击后领取一条;前台服务显示步骤;生产 App 只连接肉包后端;取消安全停止 | -| T-207 | 接入后端 VLM、候选、事件和结果回传 | T-206 | ADMIN 配置 OpenAI 兼容服务;密钥不下发 App;幂等回传且管理详情可审计 | +| T-206 | App 接入领取、有限离线执行和进度 | T-202、T-205 | 用户点击后领取一条;默认 30 分钟离线授权;到期/取消安全停止;不重复执行 | +| T-207 | 接入 App 本地 VLM、候选、事件和结果回传 | T-206 | 保留独立模式;Key 加密;后端不代理模型;幂等补报且管理详情可审计 | | T-208 | 建立候选决策数据与人工理由闭环 | T-207 | 观测/预测/推荐/人工标签分离;逐候选结构化理由;截图受控留存;不阻塞第一版流程 | ## Phase 3:端到端验证 diff --git a/docs/07-user-stories.md b/docs/07-user-stories.md index eb75e16..1e9f16a 100644 --- a/docs/07-user-stories.md +++ b/docs/07-user-stories.md @@ -15,7 +15,7 @@ | US-006 | 看懂失败并安全恢复 | P0 | 采购执行员、采购管理员 | 知道失败位置和下一步,避免重复操作 | F-007 | IX-008 | 已定 | | US-007 | 建立受控会话和设备身份 | P0 | 采购管理员、采购执行员 | 未授权人员和设备不能接触任务 | F-001、F-003 | IX-001、IX-004 | 已定 | | US-008 | 积累可信的候选决策样本 | P1 | 采购执行员、优化人员 | 用真实曝光和人工理由评估并改进模型 | F-008 | IX-009 | 已定,T-208 后置 | -| US-009 | 集中管理 VLM 服务 | P0 | 采购管理员 | 安全切换兼容服务而不向采购手机分发密钥 | F-009 | IX-010 | 已定,T-207 | +| US-009 | 手机独立完成采购任务 | P0 | 采购执行员 | 后端短时不可用时仍在有限授权内安全执行 | F-009 | IX-010 | 已定,T-206/T-207 | ## US-001 创建清晰的采购任务 @@ -182,27 +182,29 @@ 4. 人工修正保留旧版本、actor 和时间;离线指标只使用当前有效人工 review。 5. 外链失效后,授权人员仍能读取保留期内的受控截图证据。 -## US-009 集中管理 VLM 服务 +## US-009 手机独立完成采购任务 -- 关联页面:管理 Web `/settings/vlm`;Android 设置页只显示后端 AI 状态。 -- 前置条件:管理员已登录;后端已配置加密 master key。 +- 关联页面:Android“设备设置”“任务执行”和“结果同步”。 +- 前置条件:设备已绑定;本地执行模式已选择;AI 模式已配置可用 provider。 -作为采购管理员,我想在后端配置 OpenAI 兼容的 VLM 服务,从而可以统一切换模型、 -控制调用和审计 provenance,而不需要采购员在每台手机保存供应商 API Key。 +作为采购执行员,我希望手机从后台领取完整任务后独立完成 VLM、拼多多操作和人工 +确认,从而在管理后端短时不可用时仍可工作,并在恢复连接后可靠提交结果。 **范围** -- 包含:服务名称、HTTPS Base URL、模型、Chat Completions 协议、超时、有限重试、 - 启用/默认版本、write-only API Key、脱敏连接测试和配置审计。 -- 不包含:采购员按任务选择模型、多 provider 自动路由、供应商私有协议、费用报表。 +- 包含:App 本地 provider 配置、Keystore 加密、手动/AI 模式、有限离线授权、加密 + outbox、结果 provenance 和恢复同步。 +- 不包含:无限离线执行、离线即时取消、后端模型代理、多设备统一模型或自动下单。 **验收场景** -1. ADMIN 保存合法配置后,新 execution 使用该版本;修改默认配置不改变已绑定任务。 -2. Key 保存后页面只显示“已配置”,留空更新保留原 Key,显式替换才产生新密文版本。 -3. 非 ADMIN 无法查看或修改配置;任何响应和日志都不包含 Key 或供应商原始正文。 -4. 非 HTTPS、重定向和私网/本机/metadata 目标被拒绝;连接测试使用内置脱敏样本。 -5. App 只显示后端 AI 可用状态,不能看到或覆盖 provider、模型、地址和 Key。 +1. 后台任务不包含或修改 provider、模型、地址和 Key;Roubao 独立模式继续可用。 +2. AI Key 迁移到 Keystore-backed 加密存储,结果和日志只记录非秘密 provenance。 +3. 后端断开超过旧的 90 秒租约后,App 仍可在服务端给定截止时间内继续;到期安全 + 停止,服务端不把任务分给其他设备。 +4. `MANUAL_FIRST` 不依赖 VLM;`AI_ASSISTED` 由 App 直连本地配置的 HTTPS provider, + 两种模式都停在人工确认且 `order_submitted=false`。 +5. 离线完成的结果恢复联网后幂等补报;离线期间的取消在恢复后的首个同步点处理。 ## 待确认 diff --git a/docs/08-interaction-checklist.md b/docs/08-interaction-checklist.md index 78ec76f..705e7a4 100644 --- a/docs/08-interaction-checklist.md +++ b/docs/08-interaction-checklist.md @@ -16,7 +16,7 @@ | IX-007 | US-005 | App 候选确认 | 接受/拒绝/转人工 | 停止自动化并回传人员结论 | P0 | 已定 | | IX-008 | US-006 | App/管理端错误状态 | 自动失败、取消、重试上传 | 显示结构化原因和恢复动作 | P0 | 已定 | | IX-009 | US-008 | App 候选理由/管理端决策详情 | 接受、拒绝、改选或修正 | 保存逐候选结构化人工标签 | P1 | 已定,T-208 后置 | -| IX-010 | US-009 | 管理 Web VLM 设置 | 保存、测试或启用配置版本 | 后端代理模型且密钥不下发 App | P0 | 已定,T-207 | +| IX-010 | US-009 | App 独立执行设置/同步状态 | 配置模式、离线执行或补报 | 授权内独立执行并可审计同步 | P0 | 已定,T-206/T-207 | ## IX-001 管理 Web 登录 @@ -165,7 +165,7 @@ - 页面:Android“任务执行”及拼多多前台 - 角色:采购执行员 -- 服务依赖:start、heartbeat、AI、event API 和 Android 自动化。 +- 服务依赖:start、heartbeat、App 本地 AI、event API 和 Android 自动化。 - 关联原型:[任务预览](design/android-task-preview.html)、 [任务执行](design/android-task-running.html)(T-202)。 @@ -279,37 +279,33 @@ - 理由使用 checkbox/radio 和明确 label,不依赖颜色;主要理由可用单选控件指定。 - 批量应用前显示候选数量和标题摘要,默认不选中。 -## IX-010 管理 VLM 服务 +## IX-010 App 独立执行与同步 -- 页面:管理 Web `/settings/vlm`;Android 设置页只读显示后端 AI 状态。 -- 角色:采购管理员。 -- 前置条件:后端加密 master key 已配置。 -- 服务依赖:T-207 provider 配置、加密存储、安全 HTTP transport 和连接测试。 +- 页面:Android“设备设置”“任务执行”和“结果同步”。 +- 角色:采购执行员。 +- 前置条件:设备已绑定;后台任务已领取。 +- 服务依赖:T-206 有限离线授权;T-207 Keystore provider 配置和加密 outbox。 **正常路径** -1. ADMIN 填写服务名称、HTTPS Base URL、模型、超时和重试上限,选择 - `/v1/chat/completions`,输入 write-only API Key。 -2. 保存前显示非秘密配置摘要;提交成功创建不可变新版本,并可设为新 execution 的 - 默认版本。 -3. “测试连接”使用内置脱敏文字和测试图片,不读取或发送任何采购任务。 -4. 页面显示当前默认版本、启用状态、最近测试时间和稳定结果,不显示 Key。 +1. 设置页选择 `MANUAL_FIRST` 或 `AI_ASSISTED`。AI 模式复用 Roubao 现有 provider、 + model、Base URL 和 Key 配置,Key 默认隐藏。 +2. 开始后台任务时固定本次 execution 的模式及非秘密 provenance;任务 payload 不能 + 改写本地 provider 设置。 +3. 管理后端断开时,执行页显示“离线执行”、服务端授权截止时间和待同步项目数;授权 + 内继续本地 workflow。 +4. 恢复连接后先同步任务归属和取消状态,再按依赖顺序幂等上传事件、证据和终态结果。 **状态与异常** -- Key 字段始终为空;已有 Key 只显示“已配置”。留空表示沿用,替换必须明确确认。 -- master key 缺失时禁止保存含 Key 的配置和测试真实服务,但不阻塞任务管理页面。 -- URL 非 HTTPS、指向受限网络、发生重定向/DNS rebinding 或模型字段非法时在字段 - 附近提示,不发出供应商请求。 -- 超时、鉴权失败、限流和 schema 不兼容显示稳定分类和 request ID;供应商响应正文 - 只进入受控诊断且默认不持久化。 -- 非 ADMIN 返回拒绝且不泄露配置是否存在;所有 POST 使用 CSRF 和防重复提交。 - -**App 边界** - -- App 只显示“后端 AI 可用/不可用”和可操作建议,不显示 provider 名称、模型、 - Base URL、Key 或选择控件。 -- Release App 不保留可用于后端任务的端上 provider 配置;Debug mock 与生产隔离。 +- 旧明文 Key 迁移失败或 Keystore 不可用:禁用真实 VLM 调用,允许用户显式改用 + `MANUAL_FIRST`,不能静默切换。 +- 远程 provider 非 HTTPS、发生重定向或输出无效:停止 AI 分支并转人工,不影响已有 + outbox 和任务证据。 +- 离线授权剩余 5 分钟时持续显示警告;到期时在安全检查点停止,不提供“继续离线”。 +- 离线期间无法保证即时接收管理取消;恢复连接后收到取消必须先停止再上传。 +- 结果补报失败保留加密本地记录和原幂等 key;不能恢复终态后的自动化。 +- App 可以显示 provider 名称和模型,但不得回显 Key、Authorization 或完整敏感 URL。 ## 通用交互约束 diff --git a/docs/README.md b/docs/README.md index 2d7226e..d6091be 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,6 +6,8 @@ AI 辅助完成拼多多检索和候选判断的系统;第一版先验证“创建 -> 领取 -> 搜索 -> 人工确认 -> 回传”的最小闭环。 +VLM 和拼多多工作流在手机本地执行;管理后端只负责任务控制、取消和结果审计。 + ## 核心文档 - [`../AGENTS.md`](../AGENTS.md):AI coding agent 的仓库级权威规则。 diff --git a/docs/api.md b/docs/api.md index e35a8eb..51e7ab0 100644 --- a/docs/api.md +++ b/docs/api.md @@ -305,6 +305,8 @@ SHA-256,响应和日志都不回显原 token 或 hash。 "sku": "BLACK-20L", "description": "容量约20L,外观接近参考图", "image_asset_id": "2bbc1bf2-30f7-497e-a52b-bcb4c61d57c3", + "reference_image_sha256": "64-char-lowercase-hex", + "task_content_sha256": "64-char-lowercase-hex", "reference_image_url": "/api/v1/tasks/37c9c715-9b51-4ed5-984e-66dad2710c71/reference-image?claim_generation=1", "quantity": 2, "max_budget": "200.00", @@ -319,6 +321,10 @@ SHA-256,响应和日志都不回显原 token 或 hash。 } ``` +`task_content_sha256` 是服务端对不可变任务内容生成的版本化摘要,客户端视为 opaque +并在候选和终态结果中原样回显;服务端拒绝与当前 execution 快照不一致的摘要。 +App 下载参考图后必须独立校验 `reference_image_sha256`。 + 当前设备自己有已过期 `CLAIMED` 时优先回收该任务,避免设备唯一归属冲突;否则按 `created_at ASC, id ASC` 选择 `PENDING` 或已过期 `CLAIMED`。没有任务返回 `204`; 同 key 的无任务重放始终保持 `204`。同 key、同 token 的活跃 claim 重放返回同一 @@ -347,7 +353,9 @@ generation/token 且租约未过期的活跃任务可以读取。成功返回匿 同 key、同请求重放返回同一 execution;错误用户/设备/token 返回 `403`,过期租约、 状态或版本冲突返回 `409`。成功响应包含更新后的 `task`、`execution`、`replayed` -和 `server_time`,运行租约使用配置的 running lease。 +和 `server_time`。T-206 将默认 running lease 从当前 90 秒改为 30 分钟(允许配置 +5 至 120 分钟),并显式返回 `execution_expires_at`。该时间是 App 可以离线继续 +自动化的上限,不是后台自动重分配时间。 ### `POST /api/v1/tasks/{task_id}/heartbeat` @@ -368,12 +376,13 @@ generation/token 且租约未过期的活跃任务可以读取。成功返回匿 "status": "RUNNING", "version": 4, "claim_generation": 1, - "claim_expires_at": "2026-07-25T08:35:00Z" + "claim_expires_at": "2026-07-25T09:03:30Z" }, "execution": { "id": "e3190742-a24b-441e-b5c1-c7ed10ed342f", "current_step": "SCAN_RESULTS", - "order_submitted": false + "order_submitted": false, + "execution_expires_at": "2026-07-25T09:03:30Z" }, "cancel_requested": false, "server_time": "2026-07-25T08:33:30Z" @@ -381,8 +390,9 @@ generation/token 且租约未过期的活跃任务可以读取。成功返回匿 ``` 只接受 1 至 64 字节的大写 ASCII step。heartbeat 使用服务端 UTC 更新 execution、 -设备最近在线时间、任务 version 和租约,不写高频任务事件;过期运行租约拒绝续租, -任务保持原非终态且绝不回到领取队列。 +设备最近在线时间、任务 version 和 `execution_expires_at`,不写高频任务事件。 +App 每 30 秒 best-effort 调用;网络失败时可执行到上一次服务端截止时间。截止时间 +过期后拒绝续作,任务保持原非终态且绝不回到领取队列。 ### `POST /api/v1/tasks/{task_id}/release` @@ -402,104 +412,34 @@ App 收到取消请求并在安全检查点停止后调用: } ``` -请求带 `X-Claim-Token` 和 `Idempotency-Key`。只有匹配的未结束 execution、有效 -运行租约和已存在的管理取消请求可以确认;成功把任务置为 `CANCELED`、结束 -execution、清除 claim 秘密并追加带用户/设备 actor 的事件。同 key 重放不产生第二 -个事件。 +请求带 `X-Claim-Token` 和 `Idempotency-Key`。只有匹配的未结束 execution、设备 +归属和已存在的管理取消请求可以确认;App 已在安全检查点停止时,即使离线授权刚 +过期也允许原设备补交确认。成功把任务置为 `CANCELED`、结束 execution、清除 claim +秘密并追加带用户/设备 actor 的事件。同 key 重放不产生第二个事件。 -## AI 合约 +## App 本地 AI 边界 -生产 App 不接收 provider、Base URL、model、timeout、retry 或 API Key,也不能通过 -AI 请求覆盖这些字段。后端在 execution 第一次 AI 调用时绑定 ADMIN 配置的默认 -provider version,后续调用使用同一 provenance。管理 Web 的 `/settings/vlm` 使用 -ADMIN session + CSRF 管理追加配置版本;Key 是 write-only,任何 GET/HTML/JSON 均 -只返回 `has_secret`。连接测试使用内置脱敏样本,不读取任务内容。 +MVP 后端不实现 `/api/v1/tasks/{task_id}/ai/*`,不保存 VLM 配置或 Key,也不代理 +第三方模型。App 使用本机配置的 OpenAI 兼容 adapter 完成需求提取和候选评估;后台 +任务 payload 不能携带或覆盖 provider、Base URL、model、prompt 或 API Key。 -第一版 provider adapter 只支持 OpenAI 兼容 `/v1/chat/completions` 多模态请求。 -远程地址必须是受 SSRF/DNS rebinding 防护的 HTTPS 端点且不跟随重定向。App 看到的 -错误只包含稳定 code、可读 message 和 request ID,不包含供应商正文。 +App 支持 `MANUAL_FIRST` 和 `AI_ASSISTED`。execution 结果必须记录实际模式; +`AI_ASSISTED` 还要记录 provider ID、model、prompt/schema version、reference/ +candidate evidence SHA-256 和结构化模型判断。结果不得包含 Key、Authorization、 +完整 endpoint、订单号、店铺名或供应商原始响应正文。 -### `POST /api/v1/tasks/{task_id}/ai/extract-requirements` - -只允许当前 execution 调用。后端从任务读取原始图片和文字,客户端不能替换硬约束。 - -```json -{ - "execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f" -} -``` - -响应: - -```json -{ - "schema_version": 1, - "search_query": "黑色 20L 双肩包", - "category": "双肩包", - "attributes": [ - {"name": "color", "value": "black", "source": "text"}, - {"name": "capacity", "value": "about 20L", "source": "text"} - ], - "sku": "BLACK-20L", - "quantity": 2, - "max_budget": "200.00", - "confidence": 0.86, - "warnings": [], - "manual_review": { - "required": false, - "reasons": [] - }, - "provenance": { - "provider_id": "configured-provider", - "model": "configured-model", - "prompt_version": "requirement-extraction-v1", - "reference_image_sha256": "64-char-lowercase-hex" - } -} -``` - -`sku`、`quantity` 和 `max_budget` 必须来自原始任务,不能采用模型返回值。第一层 -`ProbeTask` 尚无预算字段,因此 T-103 输出 `max_budget: null` 并追加 -`MAX_BUDGET_NOT_PROVIDED` 警告。属性 `source` 的 API 表示使用小写 -`title/image/both`;供应商响应在 adapter 内规范化后才进入此合约。 - -### `POST /api/v1/tasks/{task_id}/ai/evaluate-candidate` - -```json -{ - "execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f", - "candidate_index": 1, - "screenshot_asset_id": "7b733922-f90f-4bc4-a9ad-3e8ec4769122", - "observed": { - "title": "页面可见标题", - "price": "189.00" - } -} -``` - -响应: - -```json -{ - "schema_version": 1, - "candidate_index": 1, - "decision": "REVIEW", - "score": 0.82, - "matched": ["颜色接近", "价格未超预算"], - "missing_or_uncertain": ["容量无法从当前页面确认"], - "rejection_reasons": [], - "confidence": 0.78 -} -``` - -`candidate_index` 必须原样回显当前候选 ordinal;`decision` 只允许 `REVIEW`、 -`REJECT`、`MANUAL_REQUIRED`。App 按 ordinal 串行评估,每个候选最多调用一次、整批 -最多 5 次,并由本地确定性规则产生建议项。模型不能返回页面动作、建议 ordinal、 -人工确认状态或订单授权,也没有“提交订单”权限;原任务未提供预算时,任何价格或 -预算匹配声明都视为无效输出。 +`sku`、`quantity` 和 `max_budget` 始终来自原任务,模型不能覆盖。App 按 ordinal +串行评估,每个 execution 最多一次需求提取、最多 5 次候选评估,并由本地确定性规则 +产生建议。模型不能返回页面动作、建议 ordinal、人工确认状态或订单授权;低置信度、 +无效 schema、证据不足或预算不确定时转人工。 ## 执行事件与结果 +App 使用加密 outbox 按“事件 -> evidence asset -> 候选 -> 终态”顺序提交。所有写接口 +重新校验 BUYER/device/task/execution/claim 和幂等键。原设备可以在 +`execution_expires_at` 后补报授权内已经产生的结果;后端记录 +`received_after_execution_expiry=true`,但这不允许 App 在过期后继续自动化。 + ### `POST /api/v1/tasks/{task_id}/events` 批量追加事件,必须带 `Idempotency-Key`: @@ -521,6 +461,53 @@ ADMIN session + CSRF 管理追加配置版本;Key 是 write-only,任何 GET/ `message` 不能包含凭证或完整个人敏感信息;同一 `event_id` 重放不重复插入。 +### `POST /api/v1/tasks/{task_id}/candidates` + +批量保存当前 execution 实际检查的最多 5 个候选,必须带 `Idempotency-Key`: + +```json +{ + "execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f", + "task_content_sha256": "64-char-lowercase-hex", + "execution_mode": "AI_ASSISTED", + "search_query": "黑色 20L 双肩包", + "provenance": { + "provider_id": "device-configured-provider", + "model": "device-configured-model", + "prompt_version": "candidate-evaluation-v1", + "schema_version": 1 + }, + "candidates": [ + { + "ordinal": 1, + "title": "页面可见标题", + "sku_text": "黑色 20L", + "price": "189.00", + "product_url": "https://mobile.yangkeduo.com/goods.html?goods_id=example", + "image_url": "https://example.invalid/short-lived-image", + "evidence_asset_ids": ["7b733922-f90f-4bc4-a9ad-3e8ec4769122"], + "evaluation": { + "decision": "REVIEW", + "score": 0.82, + "matched": ["颜色接近"], + "missing_or_uncertain": ["容量需人工确认"], + "rejection_reasons": [], + "confidence": 0.78 + } + } + ], + "recommendation": { + "candidate_ordinal": 1, + "policy_version": "local-recommendation-v1", + "reasons": ["当前证据下匹配分最高"] + } +} +``` + +`MANUAL_FIRST` 时 `provenance` 和 `evaluation` 为空,但候选观察、搜索词和人工结果仍 +可提交。后端校验 ordinal 连续唯一、最多 5 个和任务内容哈希;不请求 `product_url` +或 `image_url`,主要证据必须是已鉴权 asset。 + ### `POST /api/v1/tasks/{task_id}/complete` 人员完成确认后调用,必须带 `Idempotency-Key`: @@ -528,9 +515,12 @@ ADMIN session + CSRF 管理追加配置版本;Key 是 write-only,任何 GET/ ```json { "execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f", + "task_content_sha256": "64-char-lowercase-hex", + "execution_mode": "AI_ASSISTED", "outcome": "CANDIDATE_ACCEPTED", "operator_reason": "款式和预算符合验证要求", "candidate": { + "ordinal": 1, "title": "页面可见标题", "price": "189.00", "evidence_asset_ids": ["7b733922-f90f-4bc4-a9ad-3e8ec4769122"] diff --git a/docs/current-state.md b/docs/current-state.md index b081e25..1a19191 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -43,9 +43,11 @@ SKU/数量由本地原值回填,预算保持空,订单提交状态固定为 false - 后置数据闭环:已登记 T-208,在第一版 T-206/T-207 跑通后分离保存候选观测、 模型预测、确定性推荐和人工标签;人工接受/拒绝使用结构化理由,20 条试验依赖它 -- VLM 部署决策:生产采购 App 只连接肉包后端;T-207 由 ADMIN 配置版本化的 - OpenAI 兼容 `/v1/chat/completions` 服务,Key 在后端加密且永不下发 App; - Debug 本机 mock 继续与 Release 隔离 +- VLM 部署决策:手机本地调用已配置的 OpenAI 兼容 provider;管理后端只负责身份、 + 任务控制和结果审计,不保存/代理模型。T-207 保留 Roubao 独立模式并把端上 Key + 迁移到 Keystore-backed 加密存储 +- 离线执行决策:T-206 将当前 90 秒运行租约扩展为默认 30 分钟有限授权;heartbeat + best-effort,授权内可离线执行,到期安全停止,RUNNING 不自动重新分配 - 测试设备:OnePlus PKG110,Android 16/API 36;肉包 `1.4.2 (7)`;拼多多 `8.17.0 (81700)` - 设备就绪:肉包采购无障碍已启用并连接;拼多多首页、搜索输入、固定词结果页、 @@ -55,7 +57,7 @@ 已用 CLI 真实导入并逐字段/图片哈希验证,生成物位于被忽略的 `.local/` - 标准启动路径:`$env:RUN_START_COMMAND="1"; .\init.ps1` - 标准验证路径:`.\init.ps1` -- 当前 blocker:真实 VLM 服务地址、模型、测试凭证、成本上限和数据留存尚未确认; +- 当前 blocker:真实 VLM 服务地址、模型、设备级测试凭证、成本上限和数据留存尚未确认; 当前只支持单 SKU/JPEG;候选探针截图要求 Android 11/API 30+ ## 当前目录 @@ -77,8 +79,8 @@ | `docs/tasks/T-203.md` | DONE | 图片/任务 API、SQLite 业务层和 SSR 管理 Web | | `docs/tasks/T-204.md` | DONE | 用户、管理会话和预授权设备联合身份 | | `docs/tasks/T-205.md` | DONE | 原子 claim、租约、execution 和取消安全确认 | -| `docs/tasks/T-206.md` | TODO | App 登录、手动领取、前台执行进度和安全停止 | -| `docs/tasks/T-207.md` | TODO | 后端 VLM 配置/代理、候选、事件、截图和结果回传 | +| `docs/tasks/T-206.md` | TODO | App 领取、默认 30 分钟有限离线执行、恢复和安全停止 | +| `docs/tasks/T-207.md` | TODO | App 本地 VLM、Key 加密、候选、事件、截图和结果回传 | | `docs/design/` | 已确认 | T-202 原型索引、4 个管理页和 7 个 Android 页面 | | `deepseek总结.txt` | 已有 | 历史讨论摘要,不是正式需求权威 | | `android-buyer/` | 已有 | Roubao `main` 固定 commit 的 Android 基线 | diff --git a/docs/routes.md b/docs/routes.md index 70500e2..5bf5659 100644 --- a/docs/routes.md +++ b/docs/routes.md @@ -11,11 +11,9 @@ T-202 的离线 P0 页面入口见[原型索引](design/index.html)。原型仅 | `/tasks` | 任务列表 | 查看状态、筛选并进入详情 | US-002 | IX-003 | | `/tasks/new` | 新建任务 | 提交图片和采购约束 | US-001 | IX-002 | | `/tasks/{id}` | 任务详情 | 查看原始输入、时间线、候选和证据 | US-002、US-006 | IX-003、IX-008 | -| `/settings/vlm` | VLM 服务设置 | 配置和测试后端 OpenAI 兼容服务 | US-009 | IX-010 | MVP 登录后默认进入 `/tasks`。未登录访问受保护页面时跳转 `/login` 并携带安全的 -站内返回路径;只接受 `/tasks`、`/settings/vlm` 及其本站子路径,拒绝绝对 URL、 -`//` 和反斜杠。 +站内返回路径;只接受 `/tasks` 及其本站子路径,拒绝绝对 URL、`//` 和反斜杠。 不存在和无权限必须使用不同内部原因,但页面均不得泄露任务内容。 ## Android 页面 @@ -30,7 +28,7 @@ Android 导航名称是逻辑目的地,具体 Compose/Fragment 形式待接入 | `task/{id}/running` | 任务执行 | 显示步骤、返回拼多多、安全停止 | US-004、US-006 | IX-006、IX-008 | | `task/{id}/confirm` | 候选确认 | 接受、拒绝、无匹配或转人工;T-208 增加逐候选结构化理由 | US-005、US-008 | IX-007、IX-009 | | `task/{id}/result` | 任务结果 | 查看终态和同步状态 | US-002、US-006 | IX-007、IX-008 | -| `settings` | 设备设置 | 查看权限、版本、后端和只读 AI 可用状态 | US-003、US-007、US-009 | IX-004、IX-005、IX-010 | +| `settings` | 设备设置 | 查看权限、后端、执行模式和本地 VLM 配置 | US-003、US-007、US-009 | IX-004、IX-005、IX-010 | ## App 后端路由 @@ -45,9 +43,15 @@ Android 导航名称是逻辑目的地,具体 Compose/Fragment 形式待接入 | `POST /api/v1/tasks/{id}/heartbeat` | 更新 step、设备在线时间和运行租约 | | `POST /api/v1/tasks/{id}/release` | 未开始时 `CLAIMED -> PENDING` | | `POST /api/v1/tasks/{id}/cancel-ack` | 安全停止后确认 `CANCELED` 并结束 execution | +| `POST /api/v1/tasks/{id}/events` | 幂等补报本地执行事件 | +| `POST /api/v1/tasks/{id}/candidates` | 保存最多 5 个候选、模型判断和本地推荐 | +| `POST /api/v1/tasks/{id}/complete` | 提交人工结果,强制 `order_submitted=false` | +| `POST /api/v1/tasks/{id}/fail` | 提交结构化失败和受控证据 | claim/start/release/cancel-ack 使用相应幂等规则;参考图、start、task heartbeat、 release 和 cancel-ack 都必须匹配当前 `X-Claim-Token` 与 `claim_generation`。 +T-207 的 events/candidates/complete/fail 接受原设备对授权内已产生结果的离线补报, +并由服务端记录是否在执行授权过期后收到;后端不提供 VLM 代理路由。 ## 导航规则 @@ -58,8 +62,10 @@ release 和 cancel-ack 都必须匹配当前 `X-Claim-Token` 与 `claim_generati - 候选确认页返回不能恢复自动化。 - 登录过期时先保存非敏感本地状态,再跳转登录;重新登录后查询服务端状态。 - 设备设置不是首屏,任务主页是采购人员的工作入口。 -- 生产 App 的设置页不提供 VLM provider、模型、Base URL 或 API Key 控件;这些只由 - ADMIN 在管理 Web 配置。 +- 后台任务不能携带或修改 provider、模型、Base URL 和 API Key;App 设置由设备 + 操作者管理,Key 使用 Keystore-backed 加密存储。 +- 管理后端离线时执行页显示授权截止时间和待同步数量;到期后只能安全停止和等待 + 联网,不能领取或继续页面动作。 ## 页面组件边界 diff --git a/docs/tasks/T-206.md b/docs/tasks/T-206.md index 1df26d0..30bc47e 100644 --- a/docs/tasks/T-206.md +++ b/docs/tasks/T-206.md @@ -1,13 +1,13 @@ --- id: T-206 -title: App 接入手动领取和执行进度 +title: App 接入领取、有限离线执行和进度 phase: 2 deps: - T-202 - T-205 status: TODO created: 2026-07-27 -context_ref: 45d4d11 +context_ref: 45d1436 work_branch: null write_paths: - README.md @@ -15,6 +15,7 @@ write_paths: - backend-api/** - docs/00-ai-start-here.md - docs/02-requirements.md + - docs/03-tech-stack.md - docs/04-architecture.md - docs/05-coding-rules.md - docs/07-user-stories.md @@ -29,75 +30,89 @@ write_paths: ## 问题 / 背景 T-205 已实现 BUYER + 设备联合鉴权、readiness heartbeat、原子 claim、任务参考图、 -start、租约 heartbeat、release 和取消确认,但当前 Android 仍只消费本地 fixture。 -管理员创建的 `PENDING` 任务无法在手机领取,真实端到端流程因此被阻塞。 +start、90 秒运行租约、release 和取消确认,但 Android 仍只消费本地 fixture。 +管理员创建的 `PENDING` 任务无法在手机领取。 -现有 Roubao 基线包含端上模型服务商、Base URL 和 API Key 设置。正式采购工作流必须 -把模型调用收口到肉包后端,避免采购手机持有供应商密钥或由执行员改变模型策略。 +90 秒运行租约要求手机持续连接管理后端,与“手机独立完成采购,只从管理后端领取并 +回传结果”的目标冲突。T-206 需要接入后端任务,同时把运行租约扩展为有限离线执行 +授权;后台失联不能导致任务被另一台设备重复执行,也不能让手机无限离线运行。 + +Roubao 原有独立模式和端上 VLM 设置必须保留。后台任务不得下发或覆盖 VLM provider、 +Base URL、model、prompt 或 API Key。 ## 关联需求与交互 - 功能:F-003、F-007、F-009。 - 用户故事:US-003、US-006、US-007、US-009。 - 交互:IX-004、IX-005、IX-006、IX-008、IX-010。 -- 架构/API:`HttpTaskSource`、设备联合身份、claim 租约状态机、前台服务和后端托管 - VLM 边界。 +- 架构/API:`HttpTaskSource`、设备联合身份、claim/运行租约、有限离线执行、前台服务 + 和本地 outbox。 ## 已定合约 -1. 采购 App 只配置肉包后端 HTTPS 地址,不直接配置或调用 VLM 服务商。模型名称、 - Base URL 和 API Key 不进入生产采购页面、任务缓存、日志或网络响应。 -2. Debug 技术探针可保留本机直连 OpenAI 兼容 mock,但必须位于 Debug source set, - 不得进入 Release APK,也不得用于后端任务。 -3. BUYER 登录必须同时提交预授权 device ID/secret;access token、device secret、 - claim token 和幂等 key 使用 Android Keystore 支撑的安全存储。敏感值不进入 - SavedState、普通 SharedPreferences、崩溃报告或日志。 -4. App 每次领取前先发设备 heartbeat。无障碍未连接、拼多多未安装、网络不可用、 - 服务端认为设备不就绪或已有活跃任务时不生成新的 claim 请求。 -5. claim token 和幂等 key 必须在请求前持久化;结果不确定时原样重放,不能生成新 - token。领取成功后下载 task-scoped 参考图并校验媒体类型、大小和解码结果。 -6. 用户在预览页确认后调用 start 并启动前台服务;运行中每 30 秒 heartbeat。服务端 - version、generation、execution 和租约是权威状态。 -7. T-206 先验证登录、领取、预览、start、前台步骤、续租、release、取消安全停止和 - 进程恢复。真实后端 VLM、候选/截图/事件/结果回传属于 T-207。 -8. T-206 的执行进度使用 fake/受控步骤到达明确的“等待 T-207 AI 接入”结果,不把 - 未上传的本地结果伪装成服务端完成,也不触发拼多多不可逆操作。 +1. 管理后端只负责身份、任务分配、状态、取消和结果审计;App 独立负责 VLM、拼多多 + 自动化、人工确认、本地状态和待上传结果。 +2. BUYER 登录同时提交预授权 device ID/secret;access token、device secret、 + claim token 和幂等 key 使用 Android Keystore 支撑的加密存储,不进入 SavedState、 + 普通 SharedPreferences、崩溃报告或日志。 +3. App 每次领取前发送设备 heartbeat。未就绪、已有活跃任务或服务端离线时不能领取; + 用户点击后才生成并持久化 claim token 与幂等 key。 +4. `CLAIMED` 默认租约仍为 10 分钟。start 后默认运行授权从 T-205 的 90 秒调整为 + 30 分钟,可在 5 至 120 分钟范围配置;成功 heartbeat 使用服务端时间滑动续期。 +5. App 计划每 30 秒 best-effort heartbeat,以同步步骤和取消请求。heartbeat 失败时 + 可在服务端下发的 `execution_expires_at` 前继续,不能因为短时断网停止或生成新 + execution;到期仍未恢复连接时必须在安全检查点停止自动化。 +6. `RUNNING/WAITING_CONFIRMATION` 无论授权是否过期都不自动回队列。重新分配必须先 + 由人员结束旧 execution,避免两台手机同时操作同一任务。 +7. 管理取消在 App 离线期间不能保证即时生效。恢复连接后 App 必须先 heartbeat, + 收到取消标志便在下一个安全检查点停止并 `cancel-ack`,不得先继续页面动作。 +8. App 在授权内完成但暂时无法上传时,把结果、事件和证据引用写入加密 outbox。 + T-207 允许原设备在授权过期后补交终态结果,并标记 + `received_after_execution_expiry=true`;过期后不得继续自动化。 +9. App 重启先恢复加密本地 execution,再与服务端核对。无法证明任务归属、授权时间 + 或本地步骤安全时停止并等待人工处理,不领取第二条任务。 +10. Roubao 独立模式与后台任务共享现有本地 workflow/VLM 能力,但任务源和同步边界 + 独立。后台任务不能携带 provider 配置,也不能触发 App 修改本地模型设置。 +11. T-206 验证登录、领取、预览、start、前台步骤、有限离线、恢复、release 和取消; + 真实候选/截图/事件/终态回传由 T-207 完成。 ## 方案 -1. 在 Android 增加严格的后端配置、认证 client、token 安全存储和稳定错误映射; - Release 只接受 HTTPS,Debug 可显式信任开发证书或使用受控本机测试路径。 -2. 实现 `HttpTaskSource`,把 claim 响应和参考图规范化为现有 `ProbeTask`/领域输入, - workflow 不判断任务来自 fixture 还是 HTTP。 -3. 按已确认原型接入登录、任务主页、任务预览、运行和结果状态;重复点击、空队列、 - 登录过期、租约过期和服务端冲突都有稳定反馈。 -4. 使用前台服务持有当前 execution、定时 heartbeat 并轮询取消标志;取消只在 - workflow 安全检查点停止,再调用 `cancel-ack`。 -5. App 重启后先用 heartbeat/任务详情核对服务端活跃任务;不能证明可恢复时停止 - 自动化并显示人工处理,不自行领取第二条任务。 -6. 后端如缺少 App 当前任务查询所需字段,只做兼容性最小补充,不改变 T-205 的 - token、幂等、租约和状态机语义。 +1. 增加严格的后端地址、认证 client、Keystore-backed secret store、持久 task state + 和稳定错误映射。Release 后端地址只接受 HTTPS;开发证书路径与生产隔离。 +2. 实现 `HttpTaskSource`,把 claim 和 task-scoped 参考图规范化为现有领域输入; + workflow/AI/automation 不判断任务来自 fixture 还是 HTTP。 +3. 接入登录、任务主页、预览、运行和恢复状态;重复点击、空队列、登录失效、授权 + 到期和服务端冲突均使用已确认页面反馈。 +4. 扩展后端运行租约配置及 start/heartbeat 响应,显式返回 + `execution_expires_at`;保持 RUNNING 不自动回收的 T-205 安全规则。 +5. 前台服务持有 execution,best-effort heartbeat 并使用服务端时间偏移判断离线 + 截止点。网络恢复后先同步取消/归属,再恢复允许的步骤。 +6. 增加加密本地状态/outbox 基础接口;T-206 只验证恢复和安全停止,不实现 T-207 + 的完整结果 payload。 ## 验收要点 -- [ ] 预授权 BUYER 在真机登录成功,错误账号、错误设备、禁用和过期状态均不能进入 - 任务页;敏感值不出现在日志和普通存储。 -- [ ] 真机 heartbeat 后点击“获取任务”能领取管理 Web 创建的一条 `PENDING` 任务, +- [ ] 预授权 BUYER 在真机登录成功;错误账号、错误设备、禁用和过期均被拒绝,秘密 + 不出现在日志和普通存储。 +- [ ] 真机 heartbeat 后点击“获取任务”只领取管理 Web 创建的一条 `PENDING` 任务, 显示标题、SKU、数量、描述和参考图;无任务显示空状态。 -- [ ] 重复点击和模拟响应丢失不会重复领取;App 重启后能继续同一 `CLAIMED` 任务。 -- [ ] 预览可开始或释放;开始后前台服务显示步骤并按约 30 秒续租,不领取第二条任务。 -- [ ] 管理 Web 请求安全停止后,App 在安全检查点停止并确认,服务端最终为 - `CANCELED`;过期租约不会被 App 擅自续作。 -- [ ] 生产采购页面没有 VLM 服务商、Base URL 或 API Key 输入;后端任务不会读取 - Roubao 旧的端上 provider 配置;Release APK 不含 Debug mock 配置。 -- [ ] Android 单元/集成测试、后端回归测试、根 `init.ps1` 和 OnePlus PKG110 - Android 16 真机端到端 smoke 通过。 +- [ ] 重复点击和模拟响应丢失不会重复领取;App 重启能恢复同一 `CLAIMED`/RUNNING。 +- [ ] start 返回默认约 30 分钟 `execution_expires_at`;断开管理后端超过 90 秒后, + 受控 fake workflow 仍能在授权内继续,任务不会分配给第二台设备。 +- [ ] 离线授权到期后 App 安全停止;恢复网络先同步服务端状态,不擅自续作。 +- [ ] 管理取消在在线时及时处理;离线时恢复连接后的首个同步点处理并确认。 +- [ ] Roubao 原独立模式、端上 provider 选择和本地 fixture 探针回归通过;后台任务 + 不下发、覆盖或读取来自任务内容的 provider 配置。 +- [ ] Android/后端测试、根 `init.ps1` 和 OnePlus PKG110 Android 16 真机 smoke + 通过,验证过程中不提交订单。 ## 边界 -- 不实现真实 VLM 代理、候选/截图/事件、complete/fail 和人工理由回传;属于 T-207。 -- 不实现后台推送、自动领取、自动下单、支付、多任务并行或跨设备调度。 -- 不允许生产 App 绕过后端直连任意模型地址,不提交真实账号、设备 secret 或订单。 +- 不实现候选、截图、事件、complete/fail 和人工理由回传;属于 T-207。 +- 不把模型调用代理到管理后端,不实现后台 provider 配置、推送、自动领取或多任务。 +- 不承诺离线期间即时取消;不允许离线授权无限延长、自动下单或支付。 +- 不提交真实账号、设备 secret、VLM Key、订单或证据。 ## 执行记录 diff --git a/docs/tasks/T-207.md b/docs/tasks/T-207.md index fd5baf8..bf3b513 100644 --- a/docs/tasks/T-207.md +++ b/docs/tasks/T-207.md @@ -1,12 +1,12 @@ --- id: T-207 -title: 接入后端 VLM、候选、事件和结果回传 +title: 接入 App 本地 VLM、候选、事件和结果回传 phase: 2 deps: - T-206 status: TODO created: 2026-07-27 -context_ref: 45d4d11 +context_ref: 45d1436 work_branch: null write_paths: - README.md @@ -14,6 +14,7 @@ write_paths: - backend-api/** - docs/00-ai-start-here.md - docs/02-requirements.md + - docs/03-tech-stack.md - docs/04-architecture.md - docs/05-coding-rules.md - docs/07-user-stories.md @@ -27,95 +28,94 @@ write_paths: ## 问题 / 背景 -T-206 跑通任务领取和执行生命周期后,App 仍不能安全使用真实 VLM,也不能把候选、 -步骤、截图、失败和人工结论保存到后端。Roubao 原有端上 provider 设置会把供应商 -选择和密钥交给采购手机,难以统一审计、限流、切换模型和积累可比较数据。 +T-206 跑通任务领取和有限离线生命周期后,还需要把现有 App 本地 VLM、最多 5 个 +候选、截图、事件、失败和人工结论接入后台任务,并在网络恢复后可靠回传。 -第一版需要支持管理员配置一个 OpenAI 兼容 VLM 服务商,由后端代理所有模型调用; -App 只传任务/execution 标识和受控候选证据。候选优化用的完整结构化人工标签仍由 -T-208 后置,不能阻塞本任务的端到端闭环。 +手机独立执行意味着后端不保存 VLM Key、不代理模型请求,也不决定具体 provider。 +代价是不同设备的模型和提示词可能不同,因此结果必须携带非秘密 provenance,管理端 +才能区分搜索、模型和人工判断。端上 Key 只能做到受控保护,Root/受控设备仍可能在 +运行时提取,所以必须使用独立、可撤销、有限额度的设备 Key。 ## 关联需求与交互 - 功能:F-002、F-004、F-005、F-006、F-007、F-009。 - 用户故事:US-002、US-004、US-005、US-006、US-009。 - 交互:IX-003、IX-006、IX-007、IX-008、IX-010。 -- 架构/API:后端托管 VLM、OpenAI 兼容 adapter、AI 合约、执行事件/资产、 - `complete`/`fail` 和管理 Web VLM 设置。 +- 架构/API:App 本地 VLM Gateway、候选/事件/资产、加密 outbox、 + `complete`/`fail` 和管理任务详情。 ## 已定合约 -1. 生产数据流固定为 `Android App -> 肉包后端 -> VLM Provider`。BUYER 不能读取、 - 选择或覆盖 provider、Base URL、model、prompt version、timeout、retry 或 API Key。 -2. 第一版 provider 协议只支持 OpenAI 兼容 `/v1/chat/completions` 多模态请求; - `/v1/responses` 和供应商私有协议后置。管理员配置名称、HTTPS Base URL、模型、 - 超时、重试上限、启用状态和默认版本。 -3. MVP 同时只有一个默认 provider 版本供新 execution 使用。配置修改创建新版本; - execution 在首次 AI 调用时绑定不可变的 provider/model/config version 和 - prompt version,后续调用不得漂移。 -4. API Key 是 write-only secret。后端使用环境变量提供的 256 bit master key,以 - AEAD 和随机 nonce 加密后落库;AAD 绑定 provider/version。GET、HTML、日志、 - 错误、审计事件和备份说明均不回显明文。缺少 master key 时禁止保存含 Key 的配置 - 和真实调用,但仍可启动管理/任务功能。 -5. 远程端点只允许 HTTPS,禁止 userinfo、query、fragment 和重定向;解析/连接阶段 - 阻断 loopback、私网、链路本地、组播和云 metadata 地址,避免 SSRF/DNS rebinding。 - 无 Key 的 loopback HTTP 仅允许显式 Debug 测试配置,不能从生产管理页面创建。 -6. 管理员可用内置脱敏文本和测试图片检查连接,不发送订单、店铺、SKU 或任务图片。 - 测试失败只显示稳定分类和 request ID,不显示供应商响应正文或密钥。 -7. 后端从原任务读取需求图片/文字;App 不能替换 SKU、数量、预算或参考图。候选评估 - 只接受当前 execution 已上传、已鉴权和解码的截图资产。 -8. 每个 execution 最多一次需求提取、最多 5 次按 ordinal 串行候选评估。默认不重试; - 配置最多允许 1 次仅限确定未收到响应的临时网络失败,schema/4xx/安全错误不重试。 -9. provider 响应经过严格 schema、大小、置信度和确定性硬约束校验。模型不能产生 - 点击动作、候选授权、人工结论或订单授权,MVP 始终 `order_submitted=false`。 -10. App 用本地持久 outbox 幂等回传步骤事件、受控截图、候选摘要、complete/fail; - 上传失败可以补报,但不得恢复已经安全停止的自动化。 -11. T-207 保存第一版候选和模型理由及必填 `operator_reason`,足以审计完整流程; - 逐候选结构化 reason code、改选修订和训练/离线评估数据合约属于 T-208。 +1. 数据流固定为:App 从管理后端领取任务;App 直接调用本机已配置的 OpenAI 兼容 + VLM;App 操作拼多多并人工确认;App 向管理后端回传结构化结果和证据。 +2. 管理后端不保存或下发 VLM provider、Base URL、model、prompt 或 API Key,不实现 + `/tasks/{id}/ai/*` 代理接口,也不根据候选外链请求第三方网络。 +3. 后台任务不能覆盖 App 的 VLM 配置。执行开始时 App 把 provider ID、model、 + prompt version、schema version 和相关证据 SHA-256 固定为 execution provenance; + API Key、Authorization 和完整 Base URL 不进入结果。 +4. App 支持 `MANUAL_FIRST` 与 `AI_ASSISTED`。前者直接从原始标题/SKU产生有界搜索词并 + 由人员判断;后者调用本地 VLM 做需求提取和候选评估。VLM 不可用不能阻止人员完成 + 任务,但模式切换必须显式记录,不能静默降级。 +5. 现有 provider 设置保留,API Key 从普通 SharedPreferences 迁移到 + Android Keystore 包装的加密存储;迁移成功后删除旧明文,失败时不继续真实调用。 +6. 每台采购设备使用独立、可撤销、有限额度的 provider Key。Release 远程端点只允许 + HTTPS,禁止 userinfo/query/fragment/重定向;HTTP 只用于 Debug 无 Key 本机 mock。 +7. 每个 execution 最多一次需求提取、最多 5 次按 ordinal 串行候选评估。模型输出经 + 严格 schema、大小、置信度和确定性约束校验,不能授权点击、人工结论或订单提交。 +8. SKU、数量、预算和参考图来自后端原任务。App 不把订单号、店铺名、数量或本机路径 + 发给 provider,只发送必要标题、SKU、参考图和当前候选受控证据。 +9. 候选观测、模型判断、本地确定性推荐和人工理由分开序列化。T-207 要求简短 + `operator_reason`;T-208 再增加逐候选结构化 reason code 和修订版本。 +10. App 使用加密 outbox 幂等上传事件、截图、候选、complete/fail。后端以 + task/execution/device/claim 重新授权,校验任务哈希、asset、状态和幂等键。 +11. App 可以在执行授权内完成后离线上传。授权过期后的补报标记 + `received_after_execution_expiry=true`,但服务端不把补报等同于允许过期后继续 + 自动化;管理详情必须显示该审计状态。 +12. 所有终态强制 `order_submitted=false`。人员确认后不恢复自动点击,安全/取消错误 + 不重试,后端结果 API 没有订单或支付权限。 ## 方案 -1. 新增版本化 provider 配置、加密 secret、execution provenance、候选、执行事件、 - outcome/error 和 evidence 资产 migration;原始任务事实与模型派生值分表保存。 -2. 后端实现 provider 管理 usecase、SSR 设置页和 CSRF 保护的写入动作。配置更新只 - 追加版本,停用只影响新 execution,审计记录 actor、时间和非秘密字段。 -3. 实现安全 HTTP transport、OpenAI Chat Completions adapter、严格请求映射/响应 - schema、调用预算、取消、超时、重试和错误分类;普通测试只连接本机 mock。 -4. 实现 `extract-requirements`、`evaluate-candidate`、event、asset、complete 和 - fail API;全部验证 BUYER/device/claim/execution/version/租约和幂等键。 -5. Android 用后端 AI client 替换生产工作流的端上 provider client,上传受控截图, - 按最多 5 个候选执行并在硬停止点要求人工填写简短理由。 -6. 管理任务详情展示原始需求、provider provenance、候选评估、人员结论、事件和鉴权 - 证据;模型理由、确定性推荐和人员理由视觉及数据上保持分离。 -7. 清除采购模式遗留的端上 provider secret,并用测试证明 Release 不再读取或发出 - 直连 provider 请求。Debug 探针继续使用独立、被忽略的本机配置。 +1. Android 把现有 SettingsManager/provider secret 迁移到 Keystore-backed store, + 保留 Roubao 独立模式;为后台 execution 增加不可变本地 provenance snapshot。 +2. 用已有 RequirementExtractor、CandidateEvaluator 和 workflow 处理 + `HttpTaskSource` 任务,增加 `MANUAL_FIRST` 显式分支和禁止任务覆盖 provider 的 + 契约测试。 +3. 新增本地加密 execution/outbox,按事件、资产、候选和终态的依赖顺序上传;网络 + 结果不确定时复用原幂等键,进程重启后继续同步但不继续已停止的自动化。 +4. 后端新增候选、执行事件、outcome/error 和 evidence migration,以及鉴权 asset、 + events、candidates、complete、fail API;不新增 provider 表、Key 或第三方 HTTP。 +5. 完整结果保存 task/execution ID、任务内容哈希、App/Android/拼多多版本、模式、 + 搜索词、最多 5 个候选、VLM provenance/判断、本地推荐、人工结论、错误和 + `order_submitted=false`。 +6. 管理任务详情展示原始输入、设备/版本、候选、模型判断、推荐、人工理由、事件、 + 离线补报标志和鉴权证据,各层信息不合并。 ## 验收要点 -- [ ] ADMIN 能新增/更新/启用一个 OpenAI 兼容 provider 版本并执行脱敏连接测试; - 非 ADMIN/BUYER 不能访问,API Key 永不回显。 -- [ ] 数据库只含带随机 nonce 的密文;错误 master key、篡改密文和缺少 master key - 均安全失败,日志/HTML/JSON/事件不包含 Key 或供应商正文。 -- [ ] 私网、loopback、metadata、重定向、DNS rebinding 和非法 URL 被拒绝;生产只 - 连接受控 HTTPS 目标。 -- [ ] 真机领取后台任务后,需求提取和最多 5 个候选评估均经肉包后端完成,抓包/测试 - 证明 App 不直连 VLM,不携带 provider Key。 -- [ ] SKU、数量、预算和参考图只能来自原任务;低置信度、无效 schema、超预算、 - 证据不完整和模型越权输出均转人工或拒绝。 -- [ ] 事件、截图、候选、complete/fail 在断网重试后不重复,管理详情可以还原执行 - 过程;人工接受/拒绝/无匹配/转人工均要求 `operator_reason`。 -- [ ] 任一路径不提交订单,终态明确保存 `order_submitted=false`;取消和安全错误不 - 自动重试或继续点击。 +- [ ] Roubao 原有独立模式和 provider 选择可继续使用;旧 Key 成功迁移后普通 + SharedPreferences 无明文,迁移/解密失败安全阻断真实调用。 +- [ ] 后台任务 payload 无 provider 地址、模型或 Key;伪造这些字段被拒绝/忽略且 + 不能修改 App 设置。 +- [ ] `MANUAL_FIRST` 在无 VLM 时能完成候选采集和人工确认;`AI_ASSISTED` 由 App + 直连配置的 HTTPS provider,抓包/测试证明管理后端不代理模型。 +- [ ] 最多一次需求提取、最多 5 个候选评估;SKU/数量/预算保持原值,低置信度、 + 无效 schema、超预算、证据不足和越权输出转人工或拒绝。 +- [ ] 结果包含 provider/model/prompt/schema 和证据哈希但不含 Key、Authorization、 + 完整 endpoint、订单号、店铺名或供应商原始响应正文。 +- [ ] 事件、截图、候选、complete/fail 在断网、进程重启和响应丢失后不重复;授权 + 过期补报可审计,后台不会把 RUNNING 自动分给其他设备。 +- [ ] 接受、拒绝、无匹配和转人工均要求 `operator_reason`;管理详情可还原执行过程, + 并明确显示 `order_submitted=false`。 - [ ] migration up/down/up、Go unit/integration/race/vet、Android unit/instrumented、 Playwright 三视口、根 `init.ps1` 和 OnePlus PKG110 真机 smoke 通过。 ## 边界 -- 不支持 `/v1/responses`、供应商私有协议、多 provider 按任务选择、采购员切换模型、 - 自动模型路由、实时价格或成本报表。 -- 不实现 T-208 的逐候选结构化人工标签、修订历史、离线评估或训练管线。 -- 不下载客户端提交的任意商品/图片 URL,不自动提交订单、支付或绕过平台风险控制。 -- 不把真实 provider Key、订单、截图、模型正文或解密 master key 纳入 Git。 +- 不实现后端 VLM 配置/代理、多设备统一模型、自动 provider 路由或费用报表。 +- 不实现 T-208 的结构化人工标签、修订历史、离线评估或训练管线。 +- 不下载客户端提交的任意商品/图片 URL,不自动提交订单、支付或绕过风险控制。 +- 不承诺 Keystore 能抵抗已 Root/完全受控设备;不提交真实 Key、订单或证据。 ## 执行记录