8.9 KiB
任务发现统一事件游标分页计划(设计稿 v0.1)
状态:历史 v0.3 设计依据;下文“当前”均指当时的 v0.3 实现,不是现行项目内运行版本。现行 Dispatcher 项目内 v0.4 已完成本地实现与隔离验收,见
新计划、v0.4 合同及验收证据;真实 SaaS/management 仍未签收、未联调,也未部署或拨号。v0.2 与本文件 v0.3 仅保留历史;未改写已发布外部合同。
1. 目标和不变边界
当前 v0.2 首次返回全量 tasks,之后返回独立的 changes,还需处理过期游标与 410。目标是只保留一种任务列表形状:首次按页读取,后续沿同一事件游标读取更新;不再提供 changes、operation 或两套响应模式。after 是变更事件序号,不是任务 ID,也不是按任务 ID 排序的翻页位置。任务 a01 更新后可以在下一页或下一次查询中再次出现。
| 已确认方向 | 约束 |
|---|---|
统一 tasks[] |
新任务按 task_id 识别;已有任务只因 status 变化触发任务生命周期动作。其他已有字段仍用于身份、归属和一致性校验,不因其变化擅自重置运行中执行。 |
| 事件游标分页 | 首次查询和每约 30 秒的后续查询都允许多页;每页有界,Dispatcher 以返回的游标继续,直到本轮无更多任务。SaaS 不必一次返回该 D 的全部任务。 |
| 撤销 | 在同一 tasks[] 中以 status=removed 返回墓碑项;没有任务出现在某一页不等于撤销。撤销不删执行恢复、幂等或 outbox。 |
| 永不过期 | 不提供 cursor_expired/410 自动重建路径;SaaS 必须保证旧游标仍可继续看到尚未消费的任务变化及撤销。游标无效、记录丢失或无法证明连续时拒绝新执行并报错,不静默重置。 |
| 控制与队列 | MQ 的 pause/resume/stop 即时控制不等 30 秒轮询;SaaS 独占创建、绑定和退役任务队列,Dispatcher 只消费预建队列。 |
本轮仅实现并验证单 Dispatcher、单 Agent、单 Cell、单租户的本地 Mock 路径;单 D 当前任务上限 256 不因分页放宽。其他 SaaS 项目内协议维持 v0.1;不修改 contracts/upstream/v1/,不授权真实供应商、云资源、拨号或切换。
2. 项目内 v0.3 读取语义(外部未签收)
- 路径沿用
/internal/v1/dispatcher/tasks;使用现有受控 D 身份。首次从起始事件序号读取(项目草案记为after=0),以后发送上次已持久化的next_cursor。next_cursor代表本页最后一个已返回任务的事件位置,不能跳到尚未返回的全局最新事件。 - 每次 HTTP 200 都使用
tasks[]和next_cursor,不再区分snapshot、changes。tasks[]可以为空;空页表示本轮追平,游标不跳跃。非空页的游标必须前进。项目内 v0.3 已冻结为规范十进制游标、单页最多 256 项、仅响应级next_cursor,不增加has_more或逐项event_id;必须读到空页,不能凭“本页少于上限”推断结束。 - SaaS 对该 D 的任务按各自最新变更事件序号升序分页;该序号必须持久、单调且不可复用。同一任务在两次查询之间多次更新,只需返回足以表达最新状态的记录,不要求保存每次中间状态。若分页期间再次更新,新的序号必须使该任务可再次被读到;绝不能因游标前进跳过尚未返回的任务。
- 普通任务沿用已确认的身份、原值
tenant_key、归属和状态字段。removed墓碑必须至少能唯一定位原 D、租户和任务;其余必填规则由项目内 v0.3 Schema 定义;task_revision是任务状态修订,不能代替事件序号,也不从旧changes[].operation=removed猜字段。已存在任务的租户/任务归属与本地绑定冲突时拒绝处理,不静默迁移。 - Dispatcher 对新
task_id建立归属并核验 SaaS 预建队列;对已有任务按最新status更新 SaaS 状态并应用更严格的准入约束。发现页中的paused→running(即使事件较新)只更新状态和游标,不解除已持久的暂停;须收到 MQresume、重新读取单任务并确认running,才恢复原队列。stopped不可逆;removed阻止新接纳,已有在途执行、最终结果和 outbox 按原身份收口,不由轮询触发重复拨号或擅自强挂。status不变时可推进游标,但不重复执行控制动作。任务配置仍通过单独的只读任务接口核验,不让发现列表替代执行快照。
tasks[]、next_cursor 的项目内精确字段与正反例已按 v0.3 Schema 冻结并在 Mock 验证;不是 SaaS 现网或对外正式合同。逐项不带 event_id,Dispatcher 不能独立证明 SaaS 页内排序/没有漏项;完整有序返回、长期墓碑及授权绑定仍需 SaaS/业务另行签收。
3. 持久化、启动和失败边界
- 新加入或重启的 D 先从本地已提交游标继续;没有新协议游标时从起始序号逐页读取。完成首次追平、核验队列和配置前,不消费执行队列或开放新拨号;MQ 控制仍可处理。每页校验 D 身份、任务数上限、身份绑定、状态和游标连续性。
- 任务记录与本页游标必须在同一 SQLite 事务提交。进程在提交前崩溃则重读原页;提交后重读只会按同一任务身份幂等应用。启动是否已经追平须单独持久标记,不能因为读到第一页就认为拥有完整任务清单。
- 请求失败、响应不完整、页大小超限、非空页游标不前进、重复/倒退位置、身份冲突或 SQLite 提交失败时,不推进游标,不用部分列表推断缺席任务已撤销,拒绝新执行并保留现场错误日志。成功追平并重新完成当前准入核验后才可恢复;不等下一窗口自动拨号、不换线、不重拨。
- 当前 v0.2 游标不能直接解释为新事件游标。切换前需有独立版本与受控排空/恢复核验;无法证明旧任务、积压及在途执行安全收口时停止切换,不清除旧执行事实或重建执行身份。不得同时运行 v0.2 与新任务发现消费者,也不提供新接口失败后回退旧增量路径。
4. “永不过期”的实际成本和阻断项
SaaS 可以维护每个任务的最新事件序号及状态,而不必为 changes 另建完整的逐次变更响应。但 D 可能长期离线且仍持有任意旧游标,因此已撤销任务的墓碑和足以恢复顺序的序号不能提前丢弃。删除墓碑、复用序号或无声截断历史都与“永不过期”冲突。活跃任务上限 256 不限制历次撤销的累计数量;墓碑存储量和首次分页追赶耗时会持续增长。
在 SaaS/业务签收容量预算、长期保留责任、备份恢复后事件序号不回退、旧 D 游标的可读性和极端积压上限之前,不能把该方向称为可上线或有界容量方案。若这些条件无法满足,必须另行确认恢复/清理合同,而不是在实现中偷偷设置过期、忽略墓碑或返回伪造的空页。
5. 本地结果与后续外部门禁
- F07 合同:发布与当前 v0.2 并列区分的新项目内任务发现版本;冻结起始游标、排序/翻页结束、单页和总量边界、墓碑身份、状态及错误响应;提供正反例、哈希和严格 Schema。旧 v0.2 合同仅保留历史,不原地改写成已发布合同。SaaS/业务签收与项目内 Mock 通过分别标注。
- F03 实现:在独立分支以先失败的测试驱动替换配置读取、逐页应用、SQLite 游标和启动追平门禁;移除旧
changes/410 运行路径,不保留双模式或故障回退。更新发布制品的合同版本与摘要核对,保持其他 v0.1 业务接口不变。 - F09 本地验收:覆盖首轮多页、空页、同任务再次出现且状态变化、墓碑、分页并发更新、跨重启重读、控制与轮询竞态、SQLite 写失败、异常游标及错误页;证明不误删、不重拨、不绕过 SaaS 队列所有权。按本轮 P1 业务模块的覆盖率口径验证,Mock 结果不冒称真实 SaaS 或生产验收。
- F06 切换阻断:只有新合同、容量责任、版本化制品、旧状态排空及实际 SaaS 联调各自签收后,才讨论真实部署;本文不授权真实外呼、OSS、ECS、供应商消费或生产切换。
项目内完成记录:F07 v0.3 Schema/正反例/哈希与 Mock C,F03 v0.3 按页持久化及失败拒新,F09 单节点/单租户本地矩阵见 f09-local-acceptance-v0.3.md;F06 本地发布阻断见 f06-local-release-gates-v0.3.md。不把这四项本地完成记录外推为 SaaS 顺序、墓碑容量或真实部署签收。