audit: task-attempt ledger feeds real first-pass/defect/rework rates (gap 3)

- cloud/attempt_ledger.py: separate task_attempts table (not tasks
  columns) with per-attempt rows, MAX+1 attempt_no, append-only
  intent; interrupted rows kept but never counted; is_defect is
  explicit, never inferred from failure; first defect category
  wins; no-ledger tasks stay unknown (None), never backfilled.
  SQLite DDL is CREATE TABLE IF NOT EXISTS (idempotent); PG_DDL
  is the verbatim manual step for production Postgres, kept out
  of POSTGRES_SCHEMA on purpose (migrations coverage untouched).
- cloud/server.py GET /metrics-report: aggregates the ledger
  into compute_metrics records; missing ledger table degrades to
  unknown rates, not an error. Cost dual-column code untouched.
- cloud/metrics.py: docstring only (ledger is now the source);
  compute_metrics logic and all #238 semantics unchanged.
- Tests: 16 ledger tests (store/aggregate/metrics/PG adapter
  evidence) + 3 endpoint tests (real rates, old task beside
  ledger task, interrupted status non-terminal).
This commit is contained in:
host committed 2026-11-04 20:56:06 +00:00
1 parent 8a4e80c20d
commit d303dedf32
6 files changed
+639 -22

No files matched your search

+53 -4
View File
@@ -40,10 +40,55 @@ workflow——第 2 轨拥有 lint/类型/安扫,本轨不碰。
`priced_usd_micros_partial` 出现——无价永不记 0、永不折进总额。
- 未知率回 `None` + `*_known: false`,绝不报 0。
当前诚实边界(端点实测):tasks 表无 attempts/defects 列,端点
传 `None`,首过/缺陷/返工率恒为 `None`;金任务集未经人工审定,
报告恒为 `pending_human_approval`、`decision_ready: false`。接真实
输入需两项前置(见待审定清单),本轨不编造。
当前诚实边界(端点实测,审计加固补缺口第 3 项之后):tasks 表
仍无 attempts/defects 列——台账改为独立表
`task_attempts`(`cloud/attempt_ledger.py`),不改主表结构。
端点 `GET /metrics-report` 聚合台账后传给 `compute_metrics`:
有台账行的任务算出真实首过/缺陷/返工率;无台账行的老任务
仍回 `None` + `*_known: false`,绝不被算成首过或无缺陷;
台账表尚不存在(老库、或生产 PG 未手动执行 `PG_DDL`)时
端点与无台账行同等处理,不报错。金任务集未经人工审定,
报告恒为 `pending_human_approval`、`decision_ready: false`。
## 任务尝试台账(审计加固补缺口第 3 项,选型与口径)
选型:独立台账表,不改主表。列方案查询简单(一行一任务、
无关联),但把每次尝试的历史压成两个计数器——哪次尝试失败、
属哪类缺陷、顺序如何,事后无法恢复;且在生产 Postgres 上拓
宽高频 `tasks` 表比重建一张空表更重。台账表多一次聚合查询,
换来完整尝试历史与不碰主表的迁移,取舍写在
`cloud/attempt_ledger.py` 模块 docstring 里。
表 `task_attempts(task_id, attempt_no, outcome, is_defect,
defect_category, created_at)`,主键 `(task_id, attempt_no)`,
`attempt_no` 按任务 `MAX + 1` 递增、只追加不改写。口径:
- `outcome` ∈ success / failure / interrupted;`interrupted`
行只留档,不计入 attempts 也不计入 defects(平台中断自动
重跑是平台事件,不是 agent 失败;只有中断行的任务在聚合
里直接缺席,仍回未知而非 0)。
- `is_defect` 由记录方显式判定,绝不从 `failure` 自动推导
(失败重试未必是缺陷轮);`defect_category` 须配
`is_defect=True`,中断行永不能是缺陷,违例写时即
`ValueError`,不存歧义数据。任务的单类别标签取首个缺陷
行的类别(attempt_no 顺序,先到先得,确定性)。
- 迁移幂等:SQLite 侧 `CREATE TABLE IF NOT EXISTS`,重跑
不动已有行;Postgres 侧 `PG_DDL`(与 SQLite DDL 同文,只用
Postgres 合法类型与复合主键、无自增/identity 列,故无需
序列处理)**须在生产 PG 上手动执行并验证**(`\d
task_attempts` 或 information_schema 等价查询),且有意不
进 `POSTGRES_SCHEMA`(不静默拓宽迁移覆盖集);手动步骤
未做之前,Postgres 端点只会回未知率,不会报错。本机无
PG,真实 PG 验证未做;PG 证据目前只有适配层级别:`?` 占位
经 `cloud.db` 适配翻译为 `%s`、DDL 无需序列处理(测试
`TestPostgresAdapterEvidence` 钉住)。
- 未做项(有意不做,原因见模块 docstring):生命周期写
入钩子未接——worker 重试/续跑循环里哪一次算一次尝试、谁
判定 `is_defect`,接错会把平台自动续跑算成尝试、把瞬时
重试算成缺陷,正好违反本轨钉住的规则;在此之前台账只
经显式 `record_attempt` 调用填充,生产率在写钩子落地前
保持诚实未知,不自动填数。读端点不建表(只读端点不改
schema),建表只发生在写路径。
## 回流与审计标准迭代(可测试规则,非口号)
@@ -93,3 +138,7 @@ workflow——第 2 轨拥有 lint/类型/安扫,本轨不碰。
待人工项(单独列):金任务集审定清单的四项人工确认;抽查 seed
的记录与保管;生产抽查比例拍板(默认值 0.10/0.50 只是测试初值)。
台账待人工项(补缺口第 3 项之后):生产 PG 上手动执行
`task_attempts` 的 `PG_DDL` 并验证;生命周期写入钩子拍板——
哪次执行算一次尝试、谁判定 `is_defect`(在拍板前台账只经
显式调用填充,生产率保持未知)。