# 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 1. **DB 行是作业权威,`TaskRegistry` 只保存进程句柄。** 排队/执行/成败一切状态以 `CampaignAnalysisDB` / `CampaignPeriodComparisonDB` 行为准;registry 只负责当前进程内的任务句柄、幂等启动与关停。重启后凭 DB 行恢复(`recover_campaign_intelligence_jobs`),不凭 registry。 2. **结算顺序:先认领再 generating。** `claim_queued` 的条件更新是耐久幂等权威——竞争者可以观察到同一 queued 行,但只有一个能把它推到 generating;抢不到的一方静默退出,绝不重复跑领域工作。`execute` 内部的固定次序:建行(ensure_queued)→ 认领 → 校验 → generating → work → 结算。 3. **`execute(job_kind, campaign_id, …)` 是结算的唯一实现。** 幂等建行、认领、异常归一(error 截断 500 字符一处)、failed/completed 落账全部收进 `intelligence_jobs.execute`;分析与对比退化为三个小 adapter:`ensure_queued`(建行/静默放弃)、`validate`(认领后校验,失败抛携带落账字段的 `JobValidationError`)、`work_fn`(纯领域工作,返回结果 + 附加落账字段)。删除任一侧 adapter 的编排序列与截断复制。 4. **校验共享入口对齐对比先例。** 新增 `validate_analysis_request`(活动终态 → 模型),路由捕获映射 400、executor 捕获落 failed 行,两处措辞与顺序不再漂移;与既有 `validate_comparison_request` 同构。自动触发跳过守卫收敛为 `auto_intelligence_eligible` 单一判断点(活动完成自动分析 + 分析完成自动对比共用)。 5. **截断与恢复上限语义。** work 异常落账统一截断 500 字符;重启恢复 queued 行上限 3 次(`MAX_QUEUED_RECOVERY_ATTEMPTS`),超限置 failed 并注明原因——与 ADR-0011 终态纪律一致:自愈有上限,超限收敛到终态且可见。 6. **投影归位。** `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 行(此前会继续跑)——与路由校验对齐后的刻意收敛。 - 失败行的模型缺失措辞统一为路由侧完整文案(含「或为该活动指定分析模型」引导)。