合并两个不可分割的深化: Phase 2 — 智能作业结算统一(ADR-0012) - intelligence_jobs.execute(job_kind, campaign_id, ...) 作为结算的 唯一实现:建行 → 认领 → 校验 → generating → 落账,一处编排、 一处截断(500 字符)。两个 executor 退化为 ensure_queued / validate / work_fn 三个小 adapter。 - analysis.validate_analysis_request() 共享校验入口(活动终态 → 模型),路由捕获映射 400、executor 捕获落 failed 行,与 validate_comparison_request 先例同构。 - campaign_runner._auto_start_analysis 的跳过守卫收敛至 auto_intelligence_eligible 单一判断点。 - comparison.py 删除零调用的 build_comparison_payload; load_comparison_view 投影归位至 campaign_read_model。 - 新增 characterization 测试(认领竞争、重复触发、截断、恢复上限)。 Phase 3 — storage/repository.py 拆分 - AsyncJobRepository 及两个子类迁至 storage/async_job_repository.py(Phase 2 的 intelligence_jobs 与 comparison 必须 import 自该路径,故与 Phase 2 同 commit)。 - ExplorationSession / ExplorationMessage 迁至 storage/exploration_repository.py;repository.py 由 1180 行降至 约 814 行,grep 确认无残留符号。 - exploration 子模块与路由 import 全部更新;测试 import 跟随。 刻意不做:CAS 共享原语、app.py 五 registry 关停顺序归一 (ADR-0006 精神,等真实需求出现再议)。
4.5 KiB
ADR-0012: 活动智能作业结算统一——execute 深接缝与 DB 权威
状态: 已接受 日期: 2026-08-24 决策者: 架构团队 相关: ADR-0004(跨 run 聚合口径)、ADR-0006(拒绝通用条件写模块)、ADR-0011(终态纪律)、CONTEXT.md(智能分析、周期对比)
Context
智能分析与周期对比两个 executor 各自手写了一遍完整的作业结算序列:幂等建行 → claim_queued 认领 → upsert(generating) → 领域工作 → 异常截断落 failed → completed 落账 → session 关闭。两遍编排约 200 行,逐句相似又逐处微妙不同(失败行带不带 baseline、截断长度、校验时机),任何一处结算语义的修改都要在两处同步,漂移风险随每次改动累积。
同时,自动触发的跳过守卫(正式线 + 分析模型可解析)在 campaign_runner._auto_start_analysis 与 executor 的自动对比链各写了一遍;分析触发的校验(活动终态 + 模型配置)在路由与分析 executor 各写了一遍——而周期对比已有 validate_comparison_request 共享入口的先例。
Decision
-
DB 行是作业权威,
TaskRegistry只保存进程句柄。 排队/执行/成败一切状态以CampaignAnalysisDB/CampaignPeriodComparisonDB行为准;registry 只负责当前进程内的任务句柄、幂等启动与关停。重启后凭 DB 行恢复(recover_campaign_intelligence_jobs),不凭 registry。 -
结算顺序:先认领再 generating。
claim_queued的条件更新是耐久幂等权威——竞争者可以观察到同一 queued 行,但只有一个能把它推到 generating;抢不到的一方静默退出,绝不重复跑领域工作。execute内部的固定次序:建行(ensure_queued)→ 认领 → 校验 → generating → work → 结算。 -
execute(job_kind, campaign_id, …)是结算的唯一实现。 幂等建行、认领、异常归一(error 截断 500 字符一处)、failed/completed 落账全部收进intelligence_jobs.execute;分析与对比退化为三个小 adapter:ensure_queued(建行/静默放弃)、validate(认领后校验,失败抛携带落账字段的JobValidationError)、work_fn(纯领域工作,返回结果 + 附加落账字段)。删除任一侧 adapter 的编排序列与截断复制。 -
校验共享入口对齐对比先例。 新增
validate_analysis_request(活动终态 → 模型),路由捕获映射 400、executor 捕获落 failed 行,两处措辞与顺序不再漂移;与既有validate_comparison_request同构。自动触发跳过守卫收敛为auto_intelligence_eligible单一判断点(活动完成自动分析 + 分析完成自动对比共用)。 -
截断与恢复上限语义。 work 异常落账统一截断 500 字符;重启恢复 queued 行上限 3 次(
MAX_QUEUED_RECOVERY_ATTEMPTS),超限置 failed 并注明原因——与 ADR-0011 终态纪律一致:自愈有上限,超限收敛到终态且可见。 -
投影归位。
load_comparison_view(GET /comparison 响应形状)移回campaign_read_model;comparison.py只留领域工作(指纹、基线解析、机械 diff、校验、叙述)。零调用的build_comparison_payload删除。
app.py 关停顺序现状(暂不动)
关停依序:scheduler_runtime → campaign_runtime → run_registry → 智能作业 → judge_registry。顺序有意(上游先停,避免停掉的调度再派生新任务),现状有 lifespan 测试覆盖智能评估侧;在出现顺序相关的真实故障前不归一为通用机制(与 ADR-0006 拒绝通用化的立场一致)。
Considered Options(已拒绝)
- 把结算序列抽成通用 async-job 框架:两个 adapter 的校验时机、落账字段、静默放弃语义各有领域差异,通用框架的接口会比两个小 adapter 更复杂(浅模块)。等出现第三、第四种耐久作业再议。
- CAS 共享原语收编认领逻辑:本轮决策不收敛(三处条件写语义有差异),见 v3 重构计划「刻意不做」。
Consequences
- 结算语义修改只需动
execute一处;characterization 测试(认领竞争、重复触发、截断、恢复上限)锁定契约。 - executor 的校验面变宽:非终态活动直接调 executor 会落 failed 行(此前会继续跑)——与路由校验对齐后的刻意收敛。
- 失败行的模型缺失措辞统一为路由侧完整文案(含「或为该活动指定分析模型」引导)。