合并两个不可分割的深化: 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 精神,等真实需求出现再议)。
42 lines
4.5 KiB
Markdown
42 lines
4.5 KiB
Markdown
# 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 行(此前会继续跑)——与路由校验对齐后的刻意收敛。
|
||
- 失败行的模型缺失措辞统一为路由侧完整文案(含「或为该活动指定分析模型」引导)。
|