Files
codex-app-server/docs/arch-conformance-metrics.md
T
host 0db51f8145 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).
2026-11-03 02:49:25 +00:00

145 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 架构一致性进 CI 与指标闭环(审计加固第 5 步)
实现:`cloud/arch_conformance.py`(纯检查,标准库)、`cloud/metrics.py`
(纯计算,标准库)、端点 `GET /metrics-report`(`cloud/server.py`,
与 `/metrics` 同款认证与成员范围)。测试失败即 CI 失败:现有
`host-verify` 工作流已跑 `discover -s cloud/tests`,本轨未改任何
workflow——第 2 轨拥有 lint/类型/安扫,本轨不碰。
## 架构一致性检查(规则、理由、测试)
| 检查 | 规则 | 理由 | 测试 |
|---|---|---|---|
| 合同版本 | 冻结后版本只能 +1,且须有协调员批准的变更记录(声明向后兼容、列出受影响客户端;破坏性变更须有回归任务) | 契约静默漂移是并行线程分叉的根源;协议是唯一合法路径,检查执行协议而非只记录协议 | `test_arch_conformance.py::TestContractVersion`(正向:等值/批准+1/破坏性+回归任务;负向:未批准、无记录、跳号、不可解析) |
| 禁目录 | 任务只能写自有目录;`..` 与符号链接逃逸在解析后判定 | 跨目录写会 corrupt 他人 worktree | `TestDirectoryGuard`(正向:目录内;负向:`..`、symlink、混合写只报违规项)。口径复用已有定义,不另造:控制面 = 租户 work 目录(`worker._tenant_dirs` 的 `work`),技能项目 = `apps/<client>/` |
| 禁 import | 基础层(db/models/queue/logging_config)不 import 编排层;执行层(sandbox)不 import 持久化/编排层;除 db/backup/migrations 外任何 cloud 模块不得直接 `sqlite3.connect` 或 import `psycopg*`(服务层走 `cloud.db.connect` 或 Store) | 基础层上引是循环依赖前兆;执行器须可换、可假 runner 测试;驱动直连绕过 sqlite/postgres 唯一分发点 | `TestLayerImports`(每项正向+负向)+ 仓库级 `TestRepoLevelScan`(当前主线零违规,未来违规即 CI 失败)。有意不禁:`server.py` 的 `sqlite3.Row`/异常类型适配(那是在 `cloud.db` 连接上的适配,不是绕过;禁它会在基线误报) |
| 技术栈冻结 | G2 冻结表变更/删除须有批准的变更记录;新增客户端是新增不是换栈,通过 | 共享核心按冻结表构建,中途换栈使其静默失效 | `TestFrozenStack`(正向:不变/新增;负向:换栈/删除无批准) |
技能项目一键版:`check_skill_project` 把合同版本、冻结栈、目录写
三项合在一起验,`TestSkillProjectFixture` 用合成项目目录正向/负向
各验一次。静态 AST 扫描看不到 `importlib` 动态导入——这是已知
边界,不声称覆盖。
## 指标采集与报告
端点:`GET /metrics-report`(需 X-API-Key,成员范围)。报告格式
见 `cloud/metrics.py::build_report`:`golden_set`、`decision_ready`、
`metrics`、`thresholds` + `thresholds_source`、`feedback_actions`、
`feedback_consumers`。
指标口径(`compute_metrics`):
- 首过绿灯率 = 首过成功任务 / 已知 attempts 的终态任务;缺陷率 =
有缺陷任务 / 已知 defects 的终态任务;返工率 = attempts>1 任务 /
已知 attempts 的终态任务。终态 = completed/merged/failed/
merge-failed;`interrupted` 不算终态(平台中断自动重跑,算失败
是罚平台事件)。
- 每任务成本复用 #212 双列口径:有价 USD micros 与无价 actual
token 分列;成本不完整(无成本行或有无价 token)时
`total_usd_micros` 为 `None`,有价部分只以
`priced_usd_micros_partial` 出现——无价永不记 0、永不折进总额。
- 未知率回 `None` + `*_known: false`,绝不报 0。
当前诚实边界(端点实测,审计加固补缺口第 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),建表只发生在写路径。
## 回流与审计标准迭代(可测试规则,非口号)
`evaluate_feedback` 是纯函数,只产出 `auto_apply: false` 的行动
记录,不自动改路由、不自动改标准;行动由两处消费:
- 路由:`tighten_check`(某类缺陷率 > 阈值)→ 该类任务路由时加
一道审计门;`route_with_extra_audit`(首过率 < 下限)→ 全量加
审计门。消费动作本身须经协调员/人工确认后才生效。
- 审计标准:`tighten_check` / `recalibrate_check`(某检查的违规被
复核推翻率 > 阈值)各开一条标准复核项;标准文档本身的版本
变更走同一合同变更协议(`check_contract_version`,批准 +1),
测试 `TestGoldenSetAndStandardsLoop` 钉住“无批准改标准失败、
有批准通过”。误报复核规则:推翻率高只触发“复核阈值”行动,
绝不让检查自动放宽自己。
默认阈值(`DEFAULT_THRESHOLDS`:缺陷率 0.20、首过下限 0.50、
推翻率 0.30)只是测试用初值,报告标注
`thresholds_source: "default_unapproved"`;生产阈值须人工拍板后
以 `thresholds_source: "human_approved"` 传入,见待审定清单。
## 审计的审计:抽查口径(`cloud/audit_sampling.py`,纯函数)
审计结论本身要被人工抽查,抽查规则可测试、可核查,不留
“定期复核”口号:
- **必抽(比例 1.0)**:推倒重做案例必抽;第三次失败根因判为
接近完成、续轮仍 fail 的案例必抽。接近完成但续轮成功的、
第 1/2 次失败接近完成的,均不属必抽(阈值就是第三次)。
- **随机抽查**:其余案例按组抽取,普通案例默认 0.10,关键路径
案例默认 0.50;小数组 ceil 取整,绝不因四舍五入抽到 0。
抽取须带 seed(`select_sample` 无 seed 即报错),seed 记入
抽查计划,抽取结果可复现、可事后核查。
- **抽查结果处理**:抽查判审计正确 → 只记一条审计的审计记录,
原结论不动;判错 → 纠正原结论并重新触发相应流程:错发推回
→ `cancel_pushback_and_rerun_audit`;错发推倒 →
`halt_rebuild_and_restore`(旧实现按推倒规则保存在 git 历史,
可恢复);错放行 → `reopen_audit_and_pushback`;原结论未知
→ `escalate_to_human`,绝不编造触发动作。每条结果(无论对错)
都须记入审计的审计记录,未记入即视为未发生。
- **金任务审定清单**(`golden_approval_checklist`):任务清单人工
复核、每任务验收标准人工定义、样本排除、审定人+时间记录,
四项全 done 且至少留一个非样本任务才算批准;样本任务
(`sample=True` 或 id 以 `sample-` 开头)一律从金任务集中剔除
并计数,示例文件永远不算金任务。报告的 `sampling` 段在无抽查
计划时明写 pending,不暗示审计已被复核。
待人工项(单独列):金任务集审定清单的四项人工确认;抽查 seed
的记录与保管;生产抽查比例拍板(默认值 0.10/0.50 只是测试初值)。
台账待人工项(补缺口第 3 项之后):生产 PG 上手动执行
`task_attempts` 的 `PG_DDL` 并验证;生命周期写入钩子拍板——
哪次执行算一次尝试、谁判定 `is_defect`(在拍板前台账只经
显式调用填充,生产率保持未知)。