- Implement tests for the ImageProcessingWorker to validate task deduplication and policy interactions. - Create tests for operation policy to ensure proper grant handling and error responses. - Add tests for reverse image provider response structure to validate empty and malformed results. - Develop comprehensive tests for scope resolution and service validation to ensure correct behavior in various scenarios.
16 KiB
Context
现有流程以通用任务 Worker 执行视觉和 Agent 任务,并由视觉 handler 隐式创建 Agent 任务、由 Agent handler 或批次 finalizer 触发 cache_generation。这使叶子 handler 同时承担业务计算和流程推进,且当前文本搜索仅能通过 scope 级全库 generation 生效。详见 proposal.md 的 Why。
现有 tasks 控制面已具备 PostgreSQL 原子认领、租约、claim generation fencing、活动去重和跨进程 Agent lane。Task.scope_id 是叶子执行及 Agent 内部 callback 的持久化授权边界;请求上下文和 payload 不得成为 Worker 的 scope 事实。新流程必须保留视觉与 Agent Task 及这些安全属性,但不能继续让叶子 handler 或 TaskBatch finalizer 承担阶段编排。
Goals / Non-Goals
Goals:
- 建立一个面向单张图片的持久化处理 job 与专用
ImageProcessingWorker,使上传和图片库补齐走同一条可恢复路径。 - 让视觉、Agent、文本 embedding 保持为独立持久 Task,并由一个明确的图片 job 编排事实按顺序创建、复用和观察,而不是由叶子 handler 互相提交任务。
- 为文本 embedding 建立按图片版本、metadata hash 和模型版本生效的查询存储。
- 复用并抽取已有的数据库并发控制原语,维持多进程、重启、租约过期和跨 scope 写回下的正确性。
- 为 operation policy 提供稳定的“新的逻辑 Agent Task”边界,避免自动恢复重复扣量。
- 区分上传触发的自动处理和图片库一键处理:前者不重启终态失败,后者是用户对所选 scope 图片的显式重试。
Non-Goals:
- 不引入通用 DAG、任意工作流定义语言或跨图片的父子任务引擎;该 Worker 只支持固定的三阶段图片流程。
- 不在开源版实现用户、账户、订阅、支付或具体额度规则。
- 不删除显式
cache_generation维护能力,也不把它作为日常图片链的隐式后续操作。 - 不使客户端提交的
scope_id、user_id、路径、job 状态或内部回调标识成为授权凭据。
Decisions
1. 使用专用图片处理 job 编排独立叶子 Task
新增 image_processing_jobs 及其阶段关系记录。每个 job 冻结 scope_id、meme_id、目标图片 SHA-256、规范化 reverse_image_policy 和处理配置指纹;每个阶段关系保存阶段输入版本、对应叶子 task_id、状态和有限诊断,文本阶段还冻结 metadata hash 和语境版本。叶子任务继续使用现有 Task 控制面:视觉为 visual_embedding_generation,Agent 为 meme_context_generation,文本为新增 text_embedding_generation。job 的总体状态由阶段和叶子 Task 事实导出或在同一事务内更新,不能仅存于 JSON payload。终态 job 的复用必须重新对照当前阶段有效性;metadata/context hash 或反向图片策略变化时创建新的 job revision,而不是重新激活旧 job。
ImageProcessingWorker 同时承担短事务编排和三类图片叶子 Task 的专用执行控制面。一次 job reconcile 只检查产物与叶子 Task 状态,并创建或复用当前唯一可运行的叶子 Task,不持有父 job claim 等待模型执行。叶子 Task 由专用 Worker 使用既有 claim/lease/fencing 语义执行;任务完成后唤醒或由扫描器重新 reconcile 父 job。现有 PostgresTaskWorkerManager 不再注册、扫描或认领这三类图片任务,只继续处理 cache_generation、metadata_repair 等其它系统任务。
替代方案是把父 job 塞入 tasks 并由 handler 阻塞等待子任务。它需要为 waiting 重新定义现有任务状态并长期占用 Worker 资源。另一个被否决的方案是取消叶子 Task、让 job 直接执行全部模型阶段;这会丢失 Agent task_id、任务活跃度、内部 callback 的 Task.scope_id 事实和既有 claim/retry 契约。复用 TaskBatch 也不成立,因为它没有有序依赖,且会在成员失败后继续收束。
2. 抽取而非复制租约、认领和 fencing 控制面
将现有 PostgreSQL claim/lease/heartbeat/claim-generation 模式抽取为专用 Worker 和现有 Worker 共用的窄数据库原语。图片 job 的短事务 reconcile 与三类叶子 Task 的认领都必须使用事务锁和跳过已锁行;任何任务进度、终态、产物写入、阶段关系更新和后续 job 唤醒都必须匹配 Task.scope_id、lease owner 和 claim generation,并核验父 job scope 与目标版本。
任务类型必须有唯一执行控制面:visual_embedding_generation、meme_context_generation、text_embedding_generation 只由 ImageProcessingWorker 扫描和认领,现有 Worker 明确排除这些类型。路由使用服务端固定的 task-type ownership 集合,不接受 payload 或客户端选择 Worker。
视觉、Agent 和 embedding 分别配置资源预算。Agent 阶段继续使用跨进程的 Agent lane;视觉和 embedding 使用独立的有界预算,避免任一种工作耗尽另两种资源。job worker 的扫描与短事务不占用 Agent lane。
替代方案是新建内存队列或在每个 scope 启动一个 Worker。前者无法在重启后恢复且容易跨进程重复,后者会放大线程与并发上限并错误依赖默认 scope。
3. 阶段有效性优先于历史任务状态
Worker 每次推进时在短事务内重新读取目标 Meme、图片 SHA-256 与对应产物。阶段是否需要运行由以下事实决定:
- 视觉阶段:目标 SHA、视觉模型和预处理版本匹配的有效视觉向量;
- Agent 阶段:目标 SHA、语境结果版本、Agent 配置和冻结的
reverse_image_policy匹配的有效语境; - 文本阶段:目标 SHA、由白名单语境字段计算的 metadata hash 与 embedding 模型匹配的有效文本向量。
连续有效的前置阶段直接标记为复用;第一个缺失或过期阶段变为唯一可运行阶段。目标删除、scope 不匹配或 SHA 变化时 job 进入 target_changed,不允许依据旧路径写入任何产物。成功阶段之后的输入发生变化会创建或复用新的目标签名 job,而不是把旧 job 的成功状态改写为新图片成功。
终态失败、blocked 或 unknown_execution job 不得被上传后的自动 enqueue 重新激活或自动替换为新 revision。图片库一键处理属于用户显式重试:它为这些终态 job 创建新的 revision,重新校验并复用仍有效的连续前置阶段,从第一个失败、阻止、未知或过期阶段继续。这样既避免上传、进程重启或 retry_at 到达时形成无限重试,也让历史失败图片可以通过同一个图片库入口恢复。
替代方案是以“最近一次任务成功”作为依据。任务记录无法证明结果仍对应当前图片、配置和语境,会在替换图片或变更模型后错误复用。
4. Agent 外部副作用使用逻辑阶段与执行尝试分离的恢复协议
图片处理 job 是外部可见的编排单位,meme_context_generation Task 是逻辑 Agent 工作单元和内部 callback 的执行身份。Worker 在确认不存在有效语境或可复用活动 Agent Task 后,预生成服务端 task id 和稳定 logical request key,并按 operation-policy capability 创建带可信 analysis.agent grant 关联的 Agent Task。grant 的 acquire/commit/release、崩溃窗口和计量语义只由该 capability 定义;本 change 只规定 Worker 的去重、Task 创建、阶段状态和恢复集成点。
每次 Agent 外部执行 attempt 在可信 Task 元数据或关联记录中持久化不可猜测的 attempt/request/session id、输入摘要、scope、目标 SHA、冻结的 reverse_image_policy 和 Agent 配置,并引用该 Agent Task 的 grant。进程在外部调用后、结果写回前失联时,恢复者只探测同一 Task/attempt 的结果,并在结果完整、目标和 claim 约束均满足时采纳;不同 Task、attempt 或 session 的结果不得被冒认。无法证明调用未发生或无法采纳结果时,Agent Task 以 failed 终态和稳定 unknown_execution 错误码收束,父 job 的 Agent 阶段进入 unknown_execution,不得静默重复付费或联网调用。文本 Task 可以使用同类 attempt 记录防止未知副作用重复,但不消耗 analysis.agent grant。
Worker 必须在有效语境和活动 Agent Task 去重后进入 operation policy 生命周期;同一 Agent Task 的自动 retry、lease recovery 和 claim generation 变化复用原 Task/grant,failed、blocked 或 unknown_execution job 的用户显式重试则创建新 revision 和必要的新 Agent Task。不得在上传时预取 grant,也不得为 Worker attempt 自行定义第二套计量或恢复协议。
Agent acquire 返回 operation_forbidden、operation_limit_exceeded 或 operation_policy_unavailable 时,不创建或执行 Agent Task,父 job 的 Agent 阶段进入通用 blocked 并保存稳定原因和 policy 可选提供的 retry_at。retry_at 只用于状态展示和调用方决定何时显式重试;Worker 不建立定时唤醒,也不在日期变化或提示时间到达后自动创建 Task。开源 AllowAllOperationPolicy 不产生这种 policy 阻止状态。
5. 文本搜索改用按图片版本的增量向量
新增单图文本向量存储,精确唯一键为 (scope_id, meme_id, image_sha256, metadata_hash, embedding_model_version),向量维度和算法版本由固定配置约束或一并纳入有效性版本,并保存向量、维度、创建时间和来源版本。旧版本向量可以保留为不可检索历史。写入与将其标记为可检索在同一事务完成;文本 provider 返回后,写回事务必须再次锁定或读取当前 Meme 语境并以 metadata hash、scope、图片 SHA、模型版本和 claim 做 CAS;不匹配时阶段只能标记 stale/reconcile,不能标记成功。语境更新入口必须触发可被 Worker 发现的 reconcile,避免旧 hash 写回后没有新 job。
cache_generation 保持独立的显式维护路径。它不再由图片阶段创建,也不能覆盖新向量的有效性。每个 scope 持久化 legacy_only、backfill、incremental_only 等迁移状态和 epoch;一次查询只能选择一个来源。迁移期间使用旧 generation 回退时,必须逐条 join 当前 scope 的 Meme 并验证图片 SHA、metadata hash、模型、维度、语境状态和文件可访问性;无法证明有效的条目排除。图片或语境更新与切换并发时,更新在同一 scope epoch 下进入新 job,切换事务不能混入旧 epoch。切换后日常查询只读取增量向量。回退窗口必须受持久化迁移状态保护,禁止把语境已变化的旧 generation 当作当前向量。
替代方案是继续把每个 job 的文本阶段接到共享全库 cache_generation。这会重新引入全库屏障、频繁重建和“某次 generation 是否包含本图”的不可判定关系。
6. 入口、状态与旧自动链一次切换
上传、单图处理、图片库一键处理和显式重试都继续接受既有 forbid|auto 业务选项,服务端负责规范化,缺失时使用 forbid。上传成功后只创建或复用图片处理 job 并返回其 ID;同一图片和配置已有活动 job 时,同策略复用,不同策略返回 generation_policy_conflict,不能并行创建两个可能覆盖同一语境的 Agent Task。如果同一目标签名只有终态失败、blocked 或 unknown_execution job,上传自动流程返回现有诊断而不创建重试 revision。图片库一键处理按页读取当前 scope 的 Meme,并为每张图片调用同一服务的显式重试模式:有效或活动 job 按策略规则复用,历史终态失败则以本次选择的策略创建新的 job revision。该服务由服务端根据已解析 scope 和数据库目标构造 job,不接受客户端提供的 scope、重试模式或阶段状态;reverse_image_policy 是受校验的业务选项,不是授权凭据。
状态 API 将图片处理 job 作为独立资源公开,返回总体、阶段状态和对应叶子 task_id;原有 Task 状态接口继续提供视觉、Agent、文本 embedding 的独立进度、活跃度和诊断。视觉 handler 内创建 Agent、Agent handler 内创建 cache generation、以及 TaskBatch 对视觉/Agent 的 finalizer 必须在同一发布中移除,防止新旧链同时推进;删除的是隐式推进责任,不是叶子 Task。
替代方案是长时间双写双调度。它会造成重复 Agent 调用、重复扣量和不同状态来源相互覆盖,因此仅允许在不调度旧后续阶段的只读迁移窗口内保留兼容读取。
Risks / Trade-offs
- [新增 job 与阶段表提高了数据库模型复杂度] → 保持固定三阶段,禁止在本变更中抽象通用 workflow;用唯一约束、外键和状态约束表达最小事实。
- [外部调用后的进程崩溃可能留下未知结果] → 保存执行尝试;优先采纳可验证结果,无法证明安全时停止并要求显式恢复,不能通过自动重放换取表面可用性。
- [增量向量迁移期间搜索可能暂时不完整] → 以 scope 迁移状态控制只读旧 generation 回退,后台补齐后原子切换;对没有可验证回退和新向量的 scope 返回明确未就绪。
- [大量图片库补齐产生突发负载] → 分页 seed、活动 job 去重、每阶段独立有界资源预算与数据库背压;不得把全库 ID 列表放进单个 payload。
- [图片库一键处理重复消耗受限 operation] → 仅为终态失败创建一个新 revision;仍有效的阶段和活动 Task 继续复用,新的 Agent Task 必须重新经过 policy acquire,单图被阻止不影响其它图片。
- [新 Worker 错误复用不同反向图片策略] → 将规范化策略冻结到 job revision、Agent Task、去重比较和结果 provenance;活动策略冲突显式拒绝,不通过建立第二个任务解决。
- [旧 Worker 或延迟执行者可能写入新链] → 删除旧自动推进路径,并以 scope、目标 SHA、lease owner 与 claim generation 共同 fencing 所有写回。
- [操作策略实现尚未落地] → 本变更定义注入点与逻辑阶段边界;在
add-operation-policy-hooks修订并实施前,开源默认 policy 始终允许,部署版不得启用计费策略。
Migration Plan
- 先完成
make-application-scope-aware的 6.1-6.4,并在同一个 keyword-only 应用工厂装配 scope factory、operation policy 与 callback verifier/issuer;再增加图片处理 job、阶段到叶子 Task 的关系、必要的执行 attempt 和单图文本向量 schema,以及索引、精确唯一约束、复合 scope/目标外键。保留现有tasks、TaskBatch和已激活 cache generation 数据。 - 发布能读取新状态但尚不调度新 job 的代码,验证迁移、租约恢复、跨 scope fencing 与搜索读路径;为每个 scope 建立持久化的向量迁移进度。
- 分页为现存图片创建或复用图片处理 job;历史任务缺失
reverse_image_policy时按forbid,并保留已有 Task 反向图片 usage 摘要和 Meme provenance。生成并校验增量文本向量;在某个 scope 的现存图片均达到可验证状态后,将该 scope 的搜索读取原子切换到增量向量。 - 启用上传与图片库补齐的
enqueue_or_reconcile,启动专用 Worker,将三类图片 Task 的唯一认领权从现有 Worker 切换给专用 Worker,并在同一发布中删除视觉到 Agent、Agent 到 cache 及批次 finalizer 的自动推进。 - 保留显式
cache_generation作为维护工具并观测 job 失败、unknown execution、claim fencing 拒绝、阶段耗时和每个 lane 的背压;确认稳定后再清理不再被读取的旧自动链数据。
回滚时先停止新 job 的认领和新入口调度,保留所有新表与已写入的阶段产物;仍有有效旧 generation 的 scope 可以切回只读旧搜索。没有可验证旧 generation 的 scope 必须返回未就绪,而不是查询可能过期的数据。不得在回滚中删除 job、文本向量或执行尝试记录。
Open Questions
- 文本 embedding provider 的成本是否需要在宿主部署中单独计量,留待定义新的 operation vocabulary 后决定;这不改变本变更中其不复用
analysis.agentgrant 的边界。