Commit Graph
12 Commits
Author SHA1 Message Date
qiaoqichaoandClaude Code b5feee6982 feat(v2): T07-a 预审结果约束与结论校验
T07 是"预审执行"大卡,按纪律拆子卡。本批为第一块:纯确定性的结论校验,
不调用任何模型、不计算概率。覆盖 AC-05/06/07/09/12/45 与 BR-05/06/07/12。

新增 services/prescreen_policy.py:
- decide():四级优先(hard_block → critical_incomplete → open_findings → clean),
  优先级顺序即业务口径,改动等于改结论,已显式标注
- **硬性阻断不可覆盖**:命中 hard 规则即 stop,并指出规则版本、保留模型原始
  结构化提案供审计;专项模型的正面单题判断不能翻盘(有专门反例)
- **未知不等于风险也不等于通过**:关键核查不可完成给 insufficient_information
  并列出未完成项,findings 的 result 原样保留、**不得被改写成 issue**
- **没有 second path 给概率**:probability() 不接受任何入参,模型分数无法顺手传入;
  native_metrics 里名为 probability 的指标会被 assert_no_probability_leak 拦下,
  self_reported 置信度可入档但不得变概率
- stop 明确不触碰正式审批(AC-45),预算耗尽按 E-08 列未完成项而非伪造完成

实现中修正了两处自己的测试写法:断言"不含高风险三字"会被免责说明本身命中;
断言 open_findings 为空与"unknown 属待处理"的定义矛盾。两处都改成检验真正的
不变量(结论非风险判定 + findings 不被改写)。

测试:新增 test_prescreen_policy.py 19 项。全量 368 项 OK(349 → 368);
verify.py 10/10 PASS。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 20:28:45 +08:00
qiaoqichaoandClaude Code 1bc17bb9bc feat(v2): T06-c 方案治理入口与 Tools 卡守卫,T06 收尾
T06 第三块,收尾。方案提案复用既有组件治理(registry.draft/release),
不新建发布路径;自批拒绝等既有保证有测试锁定。

- propose_plan():维度在提案时即去重与冲突拦截(T06-a 的解析前移),
  保证草稿阶段就挡住"同 ID 不同定义"
- **不得出现业务 Tools 卡**(ADR-11):方案自带 tools/tool_cards/
  allowed_tools/permissions 一律拒绝,否则业务侧可绕过工具白名单;
  工具只能由后台治理
- validate_critical_checks():关键核查项必须真由本方案的 Skill/规则产生,
  凭空声明的"关键项"会让 BR-06 的硬性阻断语义失去可追溯性

T06 三块完成,卡片验收对照:AC-28(装配与依赖拒绝)· AC-29(维度去重与刻度
冲突)· AC-44(0/2/6/9 维与异刻度的呈现判定)均有测试;"没有 Tools 卡" 有守卫。

测试:test_review_plan.py 增至 34 项。全量 349 项 OK(340 → 349);
verify.py 10/10 PASS。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 20:24:24 +08:00
qiaoqichaoandClaude Code 8a9005036c feat(v2): T06-b 方案装配与依赖预检(可定位的 E-19 文案)
T06 第二块。核对后确认 component_registry.freeze() 已实现大部分 AC-28:
按 release 指针解析依赖、原生方法文本装配时固化、循环依赖与输出冲突检查、
数据域一致性("演示与正式组件不能混用")、content_hash。因此本批**不重复实现冻结**,
只补真正的缺口。

新增 contracts:JudgmentBinding、ReviewPlan(含维度、关键核查项、专项绑定)。

新增 review_plan.check_dependencies() / assemble():
- **缺口**:未发布/已撤回的依赖原先抛裸 KeyError,被 translate() 映射成 404
  "对象不存在",用户无法定位是哪一项。现改为逐个检查并**指明依赖名与原因**
  ("尚未发布或已撤回"/"版本状态为 X"/"发布指针指向的版本不存在")
- 只用 release 指针判定可用性——草稿与已撤回天然没有指针,这正是 BR-27
  "草稿或已撤回组件不得进入新运行"的机械表达
- 装配时接入 T06-a 的维度解析与呈现判定,并做不丢失断言

测试:test_review_plan.py 增至 25 项(新增 7 项装配)。其中版本换版后
content_hash 必须变化,用于证明版本确实被冻结进结果而不是每次重解析。
全量 340 项 OK(333 → 340);verify.py 10/10 PASS。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 20:20:58 +08:00
qiaoqichaoandClaude Code 019d5081df feat(v2): T06-a 方案维度解析(去重、刻度冲突、呈现判定)
T06 是大卡,按纪律拆子卡。本批为第一块:维度契约与解析。

新增 contracts:ScaleLevel、DimensionSpec。刻度层级必须升序且取值唯一
(乱序会让展示语义倒置);同 ID 必须标签/量纲/刻度层级完全一致。

新增 services/review_plan.py:
- resolve_dimensions():按 dimension_id 合并;**同 ID 不同定义直接报冲突并拒绝**,
  不让程序或模型临时挑一个(BR-28 / E-20);相同定义只留一份并合并组件引用,
  避免重复计数
- presentation():AC-44 的呈现判定——0 维 empty / 2 维 list / 3–8 同刻度 radar /
  9 维 grouped / 异刻度 faceted;硬阻断**单列**且永不进雷达面积
- assert_nothing_dropped():三组并集必须等于全部维度,"不默默丢失关键项" 有守卫
  且守卫自身有测试证明会报错

刻度改版(同 scale_id 不同 version)视为不同定义——语义可能已变,不得共用雷达。

未完成:T06-b 方案装配与依赖冻结(AC-28)、T06-c 维度在结果中的落位属后续子卡。

测试:新增 test_review_plan.py 18 项(去重 8 + 呈现 10)。全量 333 项 OK(315 → 333);
verify.py 10/10 PASS。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 20:16:39 +08:00
qiaoqichaoandClaude Code d18c147076 feat(v2): T05-c 运行补充说明只能形成草稿(AC-26)
运行时的"本次预审补充说明"只影响本次运行(run_instruction 落在运行对象上,
属 T07 的 ScreeningRun 字段)。要变成持久知识只有一条路:显式提议为草稿,
且走正常审核。本批实现卡片侧的那一半。

新增 knowledge_cards.draft_from_run_instruction(),三条约束:
1. 只产草稿——运行中说过的话绝不直接生效(有测试断言无已发布版本产生)
2. **不得携带工具/权限/引擎字段**:allowed_tools / tools / permissions / engine /
   run_instruction 出现即拒,扩大工具授权必须走独立提议与审核
3. 记录 derived_from_run,便于回溯知识来自哪次运行
另:运行指令不能声明为 history_derived(历史提炼须提供来源事件或证据),
hard 门禁对运行衍生的卡片同样生效。

未完成:运行对象上的 run_instruction 字段与"只影响本次运行"的服务端实现属 T07,
本批只交付卡片侧,不宣称 AC-26 已整体闭环。

测试:test_knowledge_cards.py 增至 25 项(新增 5 项)。
全量 315 项 OK(310 → 315);verify.py 10/10 PASS。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 18:51:12 +08:00
qiaoqichaoandClaude Code f8c4ba9e1d feat(v2): T05-b 知识卡片与硬规则依据门禁
T05 第二步:候选与治理。设计明令"复用卡片治理、不新建第二套发布库",
故本模块**只做投影**,状态机与发布完全委托 component_registry。

新增 contracts:Origin / GovernanceStatus / RuleEffect / Condition /
RuleContent / KnowledgeCard。Condition 只允许字段+有限算子,**不执行表达式**。
RuleContent 在契约层就要求 hard 必须带 policy_ref 或 authorization_ref 且附依据证据。

新增 services/knowledge_cards.py:
- propose():投影字段(origin/谱系/范围/条件)后委托 registry.draft(),**只产草稿**
- publish():发布时**再查一次** hard 门禁——草稿可能在提议后被改,不能只信提议时的校验
- effective():按数据域/有效期/范围过滤已发布版本(BR-04)

既有治理的保证原样生效,有测试锁定:自批被 release() 拒绝、无评测不能发布、
已发布需新版本号、提议不产生已发布版本。

三处必须点明的边界与映射:
1. **同键不同义**:本设计的 data_domain ↔ 注册表的 scope(demo/production),
   而本设计的 scope 是 project/team。投影层显式互换并存为 card_scope,绝不混用;
   调用方私传 data_domain 会被拒
2. **条件形状不同**:注册表条件是 {code,field,operator,threshold,title,suggestion},
   本设计是 {field,operator,value}。做**翻译**而非强加;算子只映射注册表实际
   会产出的 5 个(gt/lt/eq/gte/lte),出现其它取值即报错而非当作已支持
3. **内置种子组件是全局的**,没有 project_id/edited_by,**不是项目知识卡片**。
   投影时明确报错,避免把内置 Skill 误当项目卡片

测试:新增 test_knowledge_cards.py 20 项(门禁 6 + 来源谱系 6 + 治理复用 5 + 有效过滤 3)。
全量 310 项 OK(290 → 310);verify.py 10/10 PASS。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 18:47:59 +08:00
qiaoqichaoandClaude Code 04ef3972a8 feat(v2): T05-a 历史过程时间线(缺时间不编造)
T05 卡片要求"先 timeline,再候选,再编辑/测试/审核"。本批是第一步。

新增 contracts:DecisionEvent(区分 occurred_at 与 recorded_at)、CaseTimeline。
新增 services/history_cases.py:确定性提炼,无模型调用。

两条硬规则的实现:
- **不编造发生时间**(BR-02):occurred_at 只取材料里记载的日期;缺失/占位值
  ("-"、"缺失"、"未知")或无法解析时保持 None 并给缺证提示。recorded_at 必填,
  但它**不表示发生时间**——有测试断言二者不相等
- **不编造经过**(14 §4):只有最终报告或非结构化材料时产出 **0 个事件** 并标
  `partial` + "过程材料不完整",绝不生成虚构的问询顺序

其它:列名靠别名表识别,**未识别的列忽略而非猜测**;未声明事件类型时保守记为
question 而不丢弃该行;事件携带行级 evidence_id 以便回溯原文定位;同项目重复提炼
是同一对象 revision 递增(03 §4);缺日期事件排序时置于末尾,不被打乱成"近期"。

实现中修掉一处自己的问题:build() 初版调了两次 store.put(首次建库 revision 会变 2),
且校验发生在落库之后——非法时间线本可能入库。现改为先校验再单次 put。

测试:新增 test_history_cases.py 18 项(不编造 6 + 提炼 6 + 隔离与版本 6),
含"缺日期为 None 且不等于导入时间""只有最终报告不得生成时间线"等反例。
全量 290 项 OK(272 → 290);verify.py 10/10 PASS。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 18:41:50 +08:00
qiaoqichaoandClaude Code 8fef00d268 feat(v2): T04 模拟案例生成与数据集划分
先边界 fixture 再批量生成;全程程序组合场景,**不做任何模型调用**(14 §8 末)。

新增 services/simulation_dataset.py:
- generate(seed, scale):程序化生成虚构项目,同 seed 同版本**逐条一致**
- split():按关联组 + 时间分桶(组内不拆),以组的最早发生时间排序
- assert_group_integrity / assert_source_allowed:组不跨集合;保留集来源禁止用于提炼
- manifest():构造 DatasetManifest 契约体

契约补 T04 才该有的部分(T00/04 当初有意推迟):EventOutcome 七值枚举、
OutcomeCount、DatasetManifest。含两条硬约束——合成数据必须 demo 域(BR-34)、
计数不得为 0(空结果用不适用表达)。

配比按 14 §8:通过 1800(直接 900 + 补件后 900)/ 未通过 600 / 不计入 600
(附条件 100 + 未送审 175 + 退出 175 + 待定 150)= 3000。
两个建模决定:
- "补件后通过"是 approved 的**子类型**(outcome_subtype),不是独立 outcome ——
  06 §3 要求区分首次提交与补件后,但设计 §9.1 的 EventOutcome 只有 7 个值,不自扩充
- conditional 是否计入目标由客户映射决定(OQ-05),故 label=None,绝不默认当通过

实现中被自己的检查抓到两个 bug:
1. 分组与近重复用了两套独立公式,导致 near_duplicate_of 指向别组记录,
   assert_group_integrity 直接报"近重复案例跨集合"。现组号与近重复由同一序号唯一推出
2. split() 末段取剩余可能为负(极端小样本),会静默切出错误的划分。已夹到非负

测试:新增 test_simulation_dataset.py 24 项(边界 fixture 10 + 生成 10 + 清单 4 +
落盘 2),含 AC-35 的可复现性与 AC-36 的组不跨集合/保留集来源拒绝。
全量 272 项 OK(248 → 272);verify.py 10/10 PASS。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 18:30:34 +08:00
qiaoqichaoandClaude Code 669e969c41 feat(v2): T03 来源定位接上契约,并锁定跨项目引用拒绝
T03 卡的要求是"复用上传/确认入口,增加导入清单、定位和冲突候选"。核对后:
导入清单与冲突候选已由 T02/S1 交付,但**"定位"只定义了契约、没有接上**——
parse() 仍只产出自由字符串 locator,SourceLocator 类型化投影是空的。
本次补上这条链路(契约与实现不一致是我的遗留)。

- assistant_materials.parse():每条 chunk 增加 source_locator(kind + 坐标),
  按 T00/04 §5.3 的映射:pdf→page、xlsx 单元格/行与 csv/txt/docx 表格→sheet+cell_range、
  docx 正文段→manual(note)
- material() 补 document_id/document_version 后**按契约校验**;漏了定位或定位不合法
  直接抛错,不落半成品进库
- **locator 自由字符串保留**:enqueue() 依赖它算证据 ID 的稳定哈希,改它会破坏 BR-13

实现中修掉一个自己引入的 bug:类型化定位的辅助函数初版命名为 table,与 docx 分支里
的表格计数器 table 同名,该分支内 helper 被整数遮蔽('int' object is not callable)。
已改名 sheet_loc。

AC-08 补测:跨项目候选引用被 supplement 拒("不属于项目"),同项目可用作对照,
未知 candidate_id 同样被拒。既有 test_project_assistant 只覆盖了跨项目文档下载。

测试:新增 test_sourcelocator.py 13 项,含真实 XLSX/DOCX fixture(zipfile 构造),
覆盖五种格式的定位派生与契约校验。全量 248 项 OK(235 → 248);verify.py 10/10 PASS。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 18:21:51 +08:00
qiaoqichaoandClaude Code fcf4d8c948 feat(v2): S1 接口层——材料导入与事实冲突的 8 个端点
T02 第三个子卡。命名空间按 T00/04 §1 定案 P1 挂 project-assistant 之下,
复用现有鉴权与同源代理,鉴权/代理代码零改动。

新增 backend/api/prescreen.py(7 路径 / 8 操作)+ test_prescreen_api.py(27 项)。
app.py 仅新增 1 个 include_router 与 1 个异常处理器注册。

落实:
- 404 统一口径(03 §2):不存在与无访问权一律 404,不泄露对象存在性。
  既有 check_project 抛 403 且被 5 个模块共用,故不改它,由本模块 scope() 包装。
  实测:无权项目返回 404 NOT_FOUND 而非 403。
- 错误信封(D2):只在新路由输出 {error:{code,message,retryable,request_id}}。
- E-09 与 E-05 精确区分:新增 store.IdempotencyConflict,不再靠字符串比对猜码。
- 客户端清单只接受 4 个字段;id/status/received_at 由服务端生成——
  状态机写权不下放。manifest 校验因此从"整对象"改为"4 字段 + 服务端补全"。

**实测发现的缺口并已修复**:Input(extra="forbid") 的校验错误由 FastAPI 校验层在
进入端点前抛出,不经过 run(),导致同一命名空间出现两种错误形状。现注册
RequestValidationError 处理器,但**只在 prescreen 路径生效**,其它路径交回
FastAPI 默认实现——既有端点形状经实测确认未变(含校验错误的形状)。

验证:
- scripts/verify.py 9/9 PASS(exit 0);全量单测 225 项 OK(196 → 225)
- claude_use/T02/smoke_s1.py 在真实 app.py 上 10/10 PASS:覆盖路由注册、
  真实前缀下的 manager 鉴权放行、404 统一、信封形状、以及既有端点未被污染

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 16:45:07 +08:00
qiaoqichaoandClaude Code c59fff68d3 feat(v2): S1 存储层——材料导入批次与事实冲突检测
T02 第二个子卡。复用 assistant_objects(新 kind),**未新建表**;旧库结构不变。

新增服务:
- services/material_import.py 批次生命周期:create/load/listing/mark_item/
  retry/cancel;状态由逐项状态派生(ready/partial/failed/parsing)
- services/fact_conflict.py 冲突检测/阻断/裁定;FactConflictUnresolved(E-17)

解决的**真实缺口**(BR-23 / AC-24):既有 apply_candidate 对 values/facts 是
后写覆盖,sources 虽累积多来源但无人检测分歧、无人阻断。本层在**新路径**前置
检测:冲突时证据仍入 sources(BR-22 留痕)但值不采用(BR-23),已确认值不被覆盖。

增量改动(均为可加性,未改既有语义与调用方行为):
- assistant_store.py:新增 idempotent_lookup / idempotent_remember 共享 helper。
  既有 enqueue 与 create_report 的内联实现**未改写**。用 INSERT OR REPLACE 而非
  裸 INSERT:重试路径上裸 INSERT 会抛 sqlite3.IntegrityError(非 Conflict,
  translate() 无法映射为 409)。
- assistant_materials.apply_candidate 新增 adopt=True 形参。adopt=False 只记证据
  不改值;既有调用方不传参,行为完全不变(有回归测试)。

设计决定:
- T00/04 曾提 3 个 kind;实现时发现契约中 ImportItem 无 revision 且内嵌于批次,
  items 是值对象,故收敛为 2 个 kind(import_batch / fact_conflict)
- E-17 有两个面:确认动作被拒 = 422;运行级结果 = HTTP 200 且冲突存于
  degradations。契约映射与注释均已写明,并有测试锁定

测试:新增 test_prescreen_storage.py(33 项)。全量 196 项 OK(124 → 196)。
scripts/verify.py 9/9 PASS(exit 0)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 14:49:03 +08:00
qiaoqichaoandClaude Code d56c699ce6 chore(v2): 从兴邦预审v2白名单抽取独立可运行基座
按 docs/v2/05-implementation-plan.md §1 候选迁移表与 claude_use/T00 白名单,
从 55e2cdb 抽取 224 个文件(2.48 MB)到独立目录 F:/mini-OpenClaw-v2。

- 排除:凭据(.env/server-deploy.conf/旧DELIVERY.md)、运行数据(xingbang_data 等
  4 个 SQLite)、依赖(venv/node_modules 符号链接)、归档(_backup*/test-evidence/
  ori_file)、Office 临时文件
- 目标目录独立 git 仓库,初始恢复点;Gitea 远程与分支待 OQ-06 Q2 定案
- 未部署、未启动服务、未调用模型

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 12:58:26 +08:00