Commit Graph

204 Commits

Author SHA1 Message Date
ThearchyHelios fa50456e0f M2 报告自查认得出引用了取自遮罩列的格子:写作者写了 [[v:Qn.rX.列]]、而这一列是遮罩列或按 SQL 认出取自遮罩列(c.phone AS tel)时,自查记一条 masked_cell,像没有出处的数字一样交回写作者重写;仍不改的按「违规处置」办——失败的判失败,标注的照常产出,时间线另起一行 report_masked_cell,节点产出写一条 issuance_gaps,出口照单收下、降档
验收里写作要求点名引用 tel,自查判 ok、violations 为空,号码随报告和可验证报告出去,一步提示都没有。写作目录不遮罩是已知设计,所以拦在自查:
- compose_doc 新增 masked({查询快照工件: 取自遮罩列的结果列}),engine/evidence.py 的 masked_cells 按它查,和句中的 [[see:]]、缺失断言一样只在组装时查;整表展开出来的格也算
- 报告节点按证据面板同一个函数(api/evidence.py 的 _snapshot_masks)算目录里每份快照的遮罩列,交给 compose_doc
- 违规、日志、出具缺口只点名引用和列名,不写那一格的值:它们会进 report.checked、事件和可验证报告
- review.py 的码表登记 report_masked_cell(degraded)
2026-10-07 00:19:21 +08:00
ThearchyHelios 76fa42849c M2 证据面板和推演认得出改了名字的遮罩列,指标本身的原值也不露:查询步骤、指标的输入、推演的勾行与遮罩、合并查询按 SQL 认取自遮罩列的结果列(c.phone AS tel 这类别名、函数、子查询、WITH),和可验证报告、核查 agent 同一套认法;用到遮罩列的指标,推演答复里的 old、rendered_old 和证据面板指标步骤的 value 写「已遮罩」
验收里证据面板对 c.phone AS tel 的快照给出 masked=[]、三个号码原样交出;推演把 phone_raw 判成 masked、输入置空,指标的 old 却还是那串号码。现在:
- 认遮罩列只有一个函数 api/evidence.py 的 _snapshot_masks(查询当时记下的加数据源现在设的,再经 engine/masking.py 的 _derived_hidden 按 SQL 认改了名字的;SQL 解析不了又写着遮罩列的整份遮),查询步骤、_InputMasks、推演的 _mask_of 都调它;_Masks 顺带记数据源类型,按它的方言解析
- 合并查询按逐格来历遮:输入快照里改了名字的遮罩列(输入是合并结果的再往下追)也遮到合并结果里
- 指标的原值按 recompute.hides_value 一个判断:有输入取自遮罩列(含只嵌在下标里的),或者最外层的输入遮没遮核对不了;推演结果标 withheld,报告里引用它的地方 rendered_old 同样写「已遮罩」
- 面板底下的遮罩说明改成「这些列取自数据源中设置了遮罩的列」:遮的可能是别名列
2026-10-07 00:13:50 +08:00
ThearchyHelios 58a6759fe2 合入期 5 前置修复 K3:核查 agent 的查询结果在工具边界先遮——取自遮罩列的结果列(别名、函数、子查询、WITH、*、UNION、表值函数、整行引用都认)在交给模型的内容和落库的快照里都换成「已遮罩」,模型看不到遮罩值;按值遮降为第二道防线;换了写法的长号码(空格、连字符、点、括号、全角、国家码)在回答、理由、工件和最后一道关里都认得出;认遮罩列的共用部分挪到 engine/masking.py 2026-10-06 22:19:42 +08:00
ThearchyHelios 39b4b6d608 合入期 5 前置修复 L1:上传表格建的数据源按导入配方里的逻辑类型认日期列,双盲复算的选择能写成业务日期;报告的结论句策略要求核对、正文却写在代码块里(结论句为 0)时,出口契约记一条缺口、不再判完整出具;compose_doc 的说明照实写 source 来自调用方 2026-10-06 22:19:34 +08:00
ThearchyHelios b0fdf56eed K3 复核修正(第 3 轮):写了库名的表按「库名.表名」查表结构;UNION 第一支按表结构数出的列名要和后面每一支一样宽才用;整行引用、函数参数里的 *、表的列名表、TABLE x、PIVOT 这类认得出
- 写了库名的表(复核 major 1):表结构以前按表名查,查询写的是另一个库里的同名表(archive.sales,列序不同)时照探查
  的那张数位置,号码原样交给模型、落库。现在表结构另按「库名.表名」记一份(库名取探查时记下的 schema、qualified 的
  前缀,masking._schema_keys),写了库名的引用按它查(_table_key),查不到的按数不清整段遮;探查时没设 schema、查询又
  写了库名的也查不到(多遮)。数位置(_star_columns、_branch_names)、认标签列(_table_lacks)都照这个查法
- 过期的表结构(复核 major 2,对 099473c 的回退):UNION 第一支是 * 时,按表结构数出的列名以前不核对列数就拿来对后一支
  的位置,表结构过期、列数已经变了也照对,遮罩值记到错的列名上。现在后面每一支数得出的列数都和它一样宽才用
  (_source_names、_width),对不上、数不清的退回整段遮
- 整行和别的 * 写法(复核 major 3):写在函数参数、表达式里的 *、s.*(to_json(s.*)、concat_ws(',', s.*)、ROW(s.*)、
  Oracle 的 JSON_OBJECT(*))按它展开的来源认(_value_columns、_scoped_star_inputs,相关子查询、FROM 里的子查询照样
  找得到外层的来源)。几种写法先改写语法树再认(_normalized,原文不动):Postgres 的整行引用(row_to_json(s)、
  json_agg(t)、SELECT s、s::text)按 Postgres 先认列的规矩,不是列的改成 ROW(s.*);带列名表的表(FROM sales AS
  s(i, r, p),引用 CTE 时写的也算)改成派生来源;解析器读错的 TABLE x 改成 SELECT * FROM x;选出项套了括号的 *
  ((s.*)、(s).*)按 * 数位置。PIVOT、UNPIVOT、MATCH_RECOGNIZE 读到遮罩列的整份遮。SQL 解析不了、里面有 *(COUNT(*)
  除外)或 TABLE x 的(UNION ALL TABLE x、带库名的 TABLE),同写着遮罩列一样整份遮(_implicit)
- Postgres 的列名表只写前几列时,不按表结构补全:别的方言里列名表比列少只能是表结构过期了,补全就丢了列数这道核对;
  这时改了名的几列整段遮(多遮),照原名的按名字认
- inquiry 模块说明的已知局限补上:写了库名的表、过期表结构的列数核对、Postgres 列名表只写前几列时整段遮、探查的不是
  默认库时没写库名的同名表可能数错、整行引用按表结构先认列(查不到时按整行多遮)、解析不了的 SQL 里的整行引用认不出

测试:新增 test_masking_whole_rows(整行、函数参数里的 *、列名表、TABLE x、Oracle 的 JSON_OBJECT(*) / UNPIVOT / PIVOT /
MATCH_RECOGNIZE、解析不了的 SQL、不碰遮罩列的照常);test_masking_star_renames 补写了库名的表(SQLite ATTACH 真跑)、
过期的表结构;test_verify_agent_masking 补核查 agent 的真实查询工具:过期的表结构,Postgres / Oracle 形状的结果(换掉
驱动)交给模型、落库、通用工件接口都遮好。每处实现都做过去掉之后测试变红的验证;复核给的 Postgres 18 探针、端到端
在临时库上重跑,都不再漏。
2026-10-06 21:50:48 +08:00
ThearchyHelios 6a7e51b81b K3 复核修正(第 2 轮):* 把遮罩列带进别的列名时按展开落到的位置遮;函数参数里的 JSON 箭头认作遮罩列;agent 查到的遮罩值和报告里的分开记,上限收满也不丢
- * 改名(复核点名的 blocker):WITH t(a, b, …) AS (SELECT * …) 这类列名表、UNION 后一支的 *、FROM 子查询或 LATERAL
  的列名表、标量子查询和 ARRAY 子查询里的 *,以前 * 本身不算取自哪一列,原值原样交给核查 agent、落库,第二道防线也
  认不出。现在 * 带出遮罩列的值时,它展开落到的那几列都算(masking._star_places):只展开一个来源、每一列都数得清的
  按位置只遮取自遮罩列的那几列——子查询按选出项数,数据源里的表按查询当时冻结的表结构数(_schema_columns;工具边界
  用数据源的 schema_cache,事后按快照的 schema_artifact,是同一份),几张列一样的月度表 SELECT * 连起来汇总,金额照常;
  数不清的(没有表结构、列数对不上、几个来源)整段遮。表结构只用来数位置,不用来断定表里没有遮罩列。标量子查询里的 *
  按 _value_columns 展开(展开数据源里的表一律算),EXISTS 里的 * 不算;递归的 CTE 不会绕不出来;带 EXCLUDE / REPLACE
  的 * 不按位置数
- JSON 箭头:函数参数里没写表名的 col->'path' 被 sqlglot 解析成以 col 为参数的 lambda,_value_columns 把 lambda 的
  参数名照列名收。SQLite json_each(profile->'$.tels')、Postgres jsonb_array_elements_text(profile->'tels')、MySQL
  JSON_UNQUOTE(profile->'$.tel') 这几种拆开、取出来的值在工具边界就遮
- 上限:run_verify_agent 以前先把调用方交进来的报告快照值并进 seen,收满 SECRETS_MAX 之后 agent 查到的值并不进去,后面
  的查询、回答时的第二道防线都认不出。现在两份分开记(masking.SeenValues:given / found),查询工具按值遮、交回调用方
  (VerifyOutcome.masked_values)时合起来,agent 查到的在前;反方审稿理由的按值遮也改成 agent 查到的先收
- 已知局限写进 inquiry 模块说明:表结构过期、列数没变却换了次序或改了名时可能数错;EXCEPT 后一支照 UNION 算(多遮)

测试:新增 test_masking_star_renames(真跑 SQLite 的各种 * 改名、按表结构数位置、表结构对不上、快照记着的表结构),
工具边界(交给模型的、落库的、通用工件接口)和审批质询端到端的 * 改名、JSON 箭头、上限收满;每处实现都做过去掉之后
测试变红的验证。
2026-10-06 20:29:58 +08:00
ThearchyHelios 6d9d31395b 出口契约认藏在代码块里的正文改为逐行标出代码:写明是文字的块不再因为一行 SQL、一行像 YAML 的键或 + / - 要点就整块当代码,公式、JSON、目录树这类像数据的行照代码看
上一轮先判整块:只要有一行以 SQL / Python 关键字或小写键开头,或者每行都以 + - 空格 @ 开头,整块就当代码,只认去掉
注释、字符串之后还成句的行。写成 markdown、text 的块里,+ / - 开头的正负面要点、正文后面另嵌的 ~~~sql、缩进的 select、
一行「- note: …」都让不带句读的结论认不出,藏进代码块反倒完整出具。反过来,写明是文字的块不去字符串,六字连写、结论词
又对公式、JSON、目录树、print、流程箭头这些行生效:结论句为 0 的报告贴一段带「占比」的公式就降档,缺口还说正文写在
代码块里。

- 逐行标出代码(_code_lines),不再整块判:块里再嵌、收得了的代码块(~~~sql、````,开了没收的那一行照普通的行看);
  SQL / Python 语句从开头那一行到这一段结束,续行也算;整段是 YAML 的(第一行是小写键,其余是键或缩进的行;写明是
  文字的块里另要有值为空的键,一行一个「- note: 一句话」是正文里的标签);像数据、公式的行(前面有字的等号和箭头、
  画目录树的制表符、JSON 的开头和「"键":」、函数调用、文件名、逗号隔开的表头)。代码的行只认去掉注释、字符串之后
  成句的,别的行照旧:写明是文字的按 _written,信息串为空的按 _prose_line
- diff:写明是文字的块里 + - 后面空一格的是列表项,紧挨着字写的才是 diff;信息串为空的块里第一个改动行是 + 的是
  正负面要点(diff 先列删掉的行),有 @@ 块头的照旧是 diff
- 一段到空行为止,只写着块注释的行不算空行;写明是文字的块里顶格的标题另起一段。块注释、三个引号括起的多行字符串
  涂成空格,行数不变
- SQL 关键字只认全小写、全大写,def / class 要写成定义的样子:「From 数据看……」「class A 门店……」是正文。代码的行里
  -- // 紧挨着前面的字也算注释
- 取舍写进 _fenced_body 的说明:一块里只要有一行在写正文就记缺口,拿不准的行宁可当代码,藏起来的正文总有别的行认得出

测试:复核里的写法逐一补上:+ / - 要点(markdown、text、信息串为空)、嵌 ~~~sql、缩进的 select、小写键的标签,正式
运行降档并只带这条缺口;带「占比」「增长率」的公式、写成 text 的 JSON 和目录树,在结论句为 0 的报告里照常完整出具。
单元参数另补 print、groupby、流程箭头、文件名、逗号表头、JSON 数组、多行 SQL 夹块注释、docstring、嵌套的 YAML、开了
没收和收尾更长的嵌套围栏、行首箭头、首字母大写的关键字、先列 + 的带 @@ diff,并钉住两条取舍:写成 text 又空一格的
diff 照列表看;信息串为空、整段都是小写键的照 YAML 看。长行耗时用例扩到七种长行、两种多行围栏、两种信息串。逐一去掉
36 处规则(diff、嵌套围栏、语句、关键字大小写、def / class、YAML、分段、像数据的七种写法、注释、块注释、多行字符串、
正文判法、结构片段、挂钩),各有用例变红。
2026-10-06 19:51:29 +08:00
ThearchyHelios 7255ae98a5 模型接入报错收尾(第 1 轮复核,后端):交给设置的核查、裁判落到演示模型照实说;给不出修复时说卡在哪里;接入名恰好是模型名那句按这一处检查器里有哪几栏写;修复规划只重算被改的节点;助手栏那一格另给一份说法;几处措辞对齐
- 「不指定接入」交给设置(核查模型、裁判模型)时:启用的接入全是演示类,设置这一级不管写的是什么,执行时落到的都是
  演示模型,说明照实带上「(演示用,不调用真实模型)」、候选带 demo 标记;一个启用的接入都没有时不算落得稳(同别处
  「换成内置演示模型」不算)。以前 land() 在判演示之前就把交给设置的一处当成落稳返回了;
- 检查器里没有下拉框、发布前检查又给不出修复时,提示末尾补「无法改用其他接入:」加第一处落不稳的地方(Landing.why,
  比如「成员「研究员」指定的模型「小模型」所属的模型接入「旧接入」已停用」),不再只剩「核对名称」让人去设置里找一个
  找不到的名字;
- 点名的接入名恰好是某个启用接入的模型:画布、发布前检查按这一处在检查器里有哪几栏写(provider_refs._model_note)——
  模型调用一类在节点的「模型」中选择它,接入在发布前检查里改用这个接入或选择「不指定接入」(修复里有哪项说哪项);
  协作成员、全图默认只说可在发布前检查里改用;核查节点、报告自己的结论句裁判两栏都在,照设置页写这两栏的叫法。
  不再叫人去一个没有的「接入」栏留空。运行时报错不分节点类型,「填写」改为「选择它」;
- 修复候选修完以后落到演示类接入的带 demo 标记,发布弹窗据此在选项名旁加醒目的标签;
- 性能:改一个节点的配置只影响它自己取模型的那几处,修复规划(autofix._ProviderPlan)每试一个候选只重算这个节点
  (provider_refs.node_uses),不再深拷贝、重算整张图;问题提示只问给不给得出(provider_fixable,找到第一个落得稳的
  就停);接入问题的引用整张图只算一遍。60 个节点都点名同一个停用接入时,画布校验里这一部分约 0.35 秒降到约 0.002 秒,
  发布前检查约 0.72 秒降到约 0.01 秒,一次应用 30 条修复约 2.7 秒降到约 0.09 秒;
- 助手:GET /api/copilot/model 另给 panel_error(写在助手栏「助手使用的模型」那一格下面,去向写「上方」,不把人指回这个
  下拉框本身);接入名恰好是模型名时并成一句,不再两次提「助手使用的模型」;PUT /api/settings 里助手那一项的 422 用
  助手报错那一句(那里只有一个选模型的下拉框,没有「换一个接入」的地方);
- 裁判设置的格式检查写「留空表示跟随助手的模型」,同设置页空选项(engine/judge.py 一行,同核查那条的改法);「交给助手」
  后只剩接入问题时写「助手无法处理」,有修复时另起半句「也可在各条问题下方选择改用的模型接入」。

测试:test_provider_ref_wording 加演示落在设置上、demo 标记、给不出修复时卡在哪里(含一个启用接入都没有)、各处的
接入名恰好是模型名、助手栏那一格的说法、助手那一项的 422、裁判格式检查、规划开销随节点数线性增长(数试算时算了多少处
取模型的地方);其余几个文件跟上新说法。新加的核心用例都做过去掉实现、测试变红的验证。
2026-10-06 19:43:44 +08:00
ThearchyHelios 099473cd6d K3 复核修正(第 1 轮):后面的查询按此前遮掉的值遮、质询工件照回答时记下的位置重放;表值函数拆开遮罩列也认;汇总遮罩列的指标不派盲算;反方理由按值遮
复核发现质询工件接口相对基线回退:agent 自己的查询快照在工具边界遮好之后,事后取质询工件收不到它另一次查询里的号码,
没设遮罩的备注、联系方式列原样给出。另有几处模型照样看得到遮罩值、或者遮多了的地方。

- 工具边界(tools/datasource、engine/masking.mask_query_result):此前已经认出的遮罩值(这个 agent 前面的查询遮掉的、
  调用方交进来的)也用来按值遮后面的结果,数据源没设遮罩的也一样。审批质询、反方审稿在派 agent 之前把报告快照里的
  遮罩值交进来(inquiry.snapshot_secrets,run_verify_agent 的 masked_values),模型从第一条查询起就看不到。已经摆在
  读者眼前的值(提问人写的、报告指标、被审的结论原文)跟着并进来的值一起免遮(_Secrets.protected);这份结果自己写
  的分档标签不按前面收的值换。
- 先查备注、后查出号码的:回答时只靠内存里的值才换掉的地方按快照记成位置(不含值,masking.view_edits),写进质询
  事件的 masked_cells,质询工件接口先照着重放(replay_edits)再按原来的办法遮。工具边界按值换掉的整格照样画成遮罩列。
  agent 查到的值先收、报告快照的后收:上限收满时不丢 agent 自己查到的。
- _derived_hidden 认 FROM、JOIN、LATERAL 里拆开遮罩列的表值函数(json_each、json_tree、unnest、jsonb_each_text、
  regexp_split_to_table、JSON_TABLE、LATERAL VALUES):按出列名认,不认得的函数又没写列名表的整份遮。
- 汇总里的 CASE 只在条件里用到遮罩列的不算取自遮罩列(同写在 WHERE 里)。双盲复算按同一套认法认出指标取自遮罩列
  (SUM(amount)、amount 设了遮罩)时,派盲算之前就记为无法复算并说清原因,不白花一次盲算(nodes/dual_check 的
  _query_input,改动在文件所有权之外,只加这一处判断)。
- 反方审稿:理由正文和理由文档按值换掉遮罩值(换了写法的也认),这句结论原文里写着的不换;重新组装时引用到的格子按
  遮好的快照渲染。
- 换了写法的号码另认不换行空格、窄空格、细空格、制表符、减号,最多四个分隔符;SQL 的字符串、带引号的名字、注释里
  也认(不带引号的数照旧是算式),字符串里换掉的号码同一条 SQL 里写成阈值的也不留;列名同样认。
- 查询失败时报错原话里的值换掉(mask_error_text:引号里、不带引号的长数,SQL 里写着的照留)。
- 快照的 mask_columns 只记数据源设的列名,改了名字的结果列只在 masked_at_query:证据面板并各份快照的这一项从表的
  字段清单里去列,混进别名会误藏同源别的表里同名的真实字段。
- * 展开数据源里的表时按冻结的表结构认它带不带同名列,没有的照常认分档标签(J2 收紧之后的多遮)。
- 格子引用指到按值换掉的格子时照实说;上万个值的合并、免遮放进线程。

测试:质询 agent 先查号码后查备注、先查备注后查号码、另一张表的号码列两种先后,质询工件、通用工件接口、事件都没有
原值;报告快照里的值在第一条查询就遮;表值函数(SQLite、Postgres、MySQL 的写法)、汇总里 CASE 的条件、* 连着的表按
表结构认标签、新的分隔符、SQL 字符串和注释和列名、报错原话、mask_columns 不混别名、遮罩列汇总的指标不派盲算、反方
理由按值遮且免遮结论原文、上限收满时 agent 的值先收。逐处去掉实现(24 处)对应的用例都会变红。
2026-10-06 19:11:14 +08:00
ThearchyHelios 46403a0aac 出口契约认藏在代码块里的正文改为逐行看字、不看列表标记;信息串为空的块里 YAML、diff 不再当成正文
上一轮按列表标记认不带句读的行,只认 - * + • 和「1.」「1)」:中文报告常见的「1、」「一、」「(1)」、· ● 开头的行和
不带标记的行藏进代码块仍是 0 句、完整出具,不包反倒降档。同一条规则对信息串为空的块都生效,结论句为 0 的报告里贴一段
写着中文列名的 YAML 或改口径的 diff 会降档,缺口还说正文写在代码块里。

- 不再按标记认:标记写法太多,补一种漏一种。一行按正文的规矩会算一句结论(evidence._classify 认文字结论的那条:
  方向词、因果词,又不只是在下定义,且有两个以上汉字)就算在写正文,不论标记,与不包代码块时的判法一致
- 信息串写明是文字(markdown、text 这类)、里面也看不出是代码的块:另认一行里连着六个以上汉字的(表格的列名、
  门店名这类标签多在五个字以内);不当代码看,# -- 和半角引号都是正文里的符号;标题不算(正文里的标题也从不算结论)
- 块里看得出是代码(SQL、Python 开头的行,小写开头的 YAML 键,整块是 diff),不论信息串写的是什么,只认去掉注释、
  字符串之后成句的:值里、别名里的「最高」「占比」这类词不算;正文里首字母大写的「Note:」是标签,不算 YAML 的键
- 信息串为空、看不出是代码的块:去掉注释、字符串之后成句,或按正文的规矩会算结论;不带句读也不像结论的列名不算
- 围栏那两行是挂在代码块上的结构片段,判断时只看块里写的内容

测试:1、 一、 (1) · ● 和不带标记的写法藏进代码块(另有信息串为空的一种)正式运行降档并只带这条缺口;YAML 列表、
diff 在结论句为 0 的报告里照常完整出具;单元参数补上引用块、表格、粗体、短结论、正文里的 # -- 引号,以及 YAML 键、
diff、SQL 别名、写成 text / markdown 的代码、不到六个字的标签、只有标题。逐一去掉六字连写、两处结论词、代码判断
(YAML、diff 各一次)、不当代码看、结构片段过滤、小写键、标题跳过、挂钩,以及换回按标记认的旧规则,各有用例变红。
2026-10-06 18:44:21 +08:00
ThearchyHelios b7da8a9af9 出口契约认藏在代码块里的正文:只挂 [[see:]] 依据的、不带句读的列表项也认,代码里的注释和字符串不算句子,英文句子改成线性扫描
上一轮只看片段里的引用和带全角句读的行,几种写法仍会让正文藏在代码块里照样完整出具,另有几种代码被当成正文:
- [[see:]] 记在句子的 see 上、不在片段里:只用 see 挂依据的列表现在也认
- 中文报告的列表项常不带句读:以 - * + • 或「1.」「1)」开头、后面有两个以上汉字的行算成句;信息串为空、看得出是
  SQL / Python 的块里(有 SELECT、FROM、def、import 这类开头的行),以 * + - 开头的是运算的续行,不当列表项
- 信息串写成 plaintext、txt、plain 的和 text 一样算写文字的代码块
- 判句之前去掉字符串字面量(可带 f、r、b、u、N 前缀;英文的 it's 这类不动)和行尾注释(-- // #),# 开头的行、
  跨行的 /* … */ 块注释都不算:SQL 行尾的中文注释、print 里带全角标点的字符串不再让 0 句的报告降档
- 认英文句子的正则对很长、不带句末标点的一行是平方级回溯(出口复核在事件循环上同步执行),改成按词串逐个数单词,
  耗时随行长线性增长

check-decode 补一项:从 io.py 读出 GAP_FENCED_BODY,钉住它归「校验不完整」、不进核查的两组。补参数化用例和端到端
用例:只挂 see 的列表、不带句读的列表、plaintext 包住正文的正式运行降档并带这条缺口;带行尾注释的 SQL 照常完整出具;
四万多字的长行一秒内判完。
2026-10-06 18:03:12 +08:00
ThearchyHelios 04606cfe94 双盲复算写业务日期:真写按写入时的版本重读上传表格导入时认作日期的列,几个列表块写同一张表时每一块都记为日期才算
record_choices 的表结构是写入时现读的,候选里的 upload_dates 却是发起审批时读的:审批停着时上传源换了版本、把日期列
改成文字,两份拼在一起会写出一条哪个版本单独看都不会给出的人工确认。现在候选里带 upload_dates 的,真写在读数据源的
同一个会话里按当前版本重读、覆盖候选里的那份,和表结构出自同一个版本,版本没变时预告和真写仍一字不差;候选里没有
这一项的是升级前发起的审批,卡上预告的是不写,照旧不认。

recipe_dates 以前只要有一个列表块把某列记为日期就算日期列:两个工作表的列表块写同一张表、同一列一个日期一个文字时,
表形状一样(都存成 TEXT),配方校验不拦,按文字导入的那一块存的是单元格原文。现在写这一列的块都记为日期才算。

补测试:审批停着时修改配方把支付日期改成文字,裁决后照实说按文字保存、不写;没有 upload_dates 的老候选不重读、照旧
不写,带着的按写入时的版本重读后照写;两块类型不一致的列不算日期。
2026-10-06 18:03:12 +08:00
ThearchyHelios 450758185d K3:核查 agent 看到的查询结果先遮、存下的快照也遮
双盲复算、反方审稿、审批质询共用的 engine/verify_agent.py 以前把查询结果原样交给模型(db_query 自己落快照,ToolMessage
里是原样的结果),遮罩全靠审批质询事后按值认,期 4 起六轮复核每轮都找到新的漏法;运行详情「工件」页签走的通用接口
GET /api/artifacts/{id} 还把核查的查询快照原样给出。根本的修法是不让模型看到遮罩值。

- tools/datasource:查询工具加遮罩开关 mask_results(缺省关,Agent 节点、调用工具节点照旧)。打开时数据源设了遮罩的,
  先按 engine/masking.py 的 mask_query_result 遮好再交给模型、再落快照:列名直接对上的、按 SQL 认出取自遮罩列的
  (别名、函数、子查询、WITH、SELECT *,复用 _derived_hidden;SQL 解析不了又写着遮罩列的,整份结果的列都遮)整列
  换成「已遮罩」、类型记成文本;同一份结果里别的格子整格是遮罩值的换掉,文字格里夹着的那一截换掉(换了写法的号码
  也认);列名不换。结果里一列都没取自遮罩列的(数值的汇总、只数个数、只在条件里用到)原样。快照里 mask_columns
  另记下查询时遮了的、改了名字的结果列(证据面板、质询工件据此画成遮罩列),masked_at_query 记下查询时就整列遮掉的列。
  这一次遮掉的值并进 ToolContext.extra[MASKED_VALUES_KEY],只在内存里。
- verify_agent:建查询工具时和 force_readonly 一样打开遮罩开关;格子引用指到查询时就遮了的列,照实说是遮罩列、不采信;
  遮掉的值经 VerifyOutcome.masked_values 交回调用方(不进 as_dict、不往外交);系统提示补一句「已遮罩」是什么。
- 审批质询:按值遮降为第二道防线。agent 自己的快照查询时就遮好了,收不到遮罩值,改由 masked_values 并进来(_plan 的
  seen),回答、查询清单照旧按值遮;「已遮罩」本身不收成遮罩值。事后取质询工件时内存里的值已经不在,SQL 检查结果的
  片段、说明照事件里记下的那条 SQL 换(_recorded_swaps:逐词对出回答时换掉的字面量和 LIKE 里换掉的那一截)。
- 双盲复算取盲算值、反方审稿的引用、证据面板、可验证报告(带遮罩的快照只给哈希,照旧)、推演、运行对比(不收核查的
  查询)都按快照里已经是「已遮罩」照常工作,文件不用改。

测试:核查 agent 选出遮罩列(直接、别名、函数、子查询、WITH、SELECT *)时交给假模型的 ToolMessage、落库的快照、通用
工件接口、tool.end 预览、收尾交给模型的查询结果里都是「已遮罩」;没碰遮罩列的聚合查询原样、照常取值;文字格夹着的
号码换掉那一截;引用遮罩格子不采信;SQL 认不出时整份遮;开关缺省关、没设遮罩的源快照一字不差;列名不改;审批质询、
反方审稿、双盲复算各补一条端到端;质询工件的检查片段照记下的 SQL 换。逐处去掉实现(开关、并进内存的值、照记下的
SQL 换、跳过「已遮罩」、遮罩格子的说法、记下改了名字的列、文字格按子串换、不改列名)对应的用例都会变红。
2026-10-06 17:36:55 +08:00
ThearchyHelios 4673e51bb8 模型接入报错的说法与修复候选收尾(后端):修复规划只否决这次改动造成的失败、问题只认自己那一处的修复、没有修复时提示不指到发布前检查、落到演示类接入照实说;各处说法对齐
修复规划(autofix.provider_choice,原 _plan_provider 的主体,provider_refs 也用它判断给不给得出修复):
- 改动波及的一处,要是点名的接入没变、而那个接入本来就不存在或已停用,它落不稳不是这次改动造成的,不再拿它否决候选。
  以前两处互相牵连(节点点名的接入写错、模型取全图默认里停用接入的模型)时两条问题都没有修复,规划器自己卡住;
- 点名的模型接入一处一条修复,annotate 只认(编号、节点、字段)都对得上的那一条,认不出就是没有,不再退到同节点、
  同编号别处的修复(以前全图默认那条的 fix 会指到某个节点的修复上);
- 一个候选都落不稳时,问题提示不再写「或在「发布前检查」中改用其他接入」,只说去设置里核对、启用;
- 修完以后落到演示类接入(kind 为 mock)时,「不指定接入」的说明写明「演示用,不调用真实模型」,和「改用演示接入」
  那一项一致——K1 要防的就是悄悄换成演示模型。

说法:
- 双盲复算、反方审稿的问题写「……的「盲算模型 · 接入」指定的模型接入」,不说「盲算模型指定的接入」(修复标题仍按角色说);
- 点名的接入名恰好是某个启用接入的模型时,运行时报错、画布问题、保存设置的 422、助手的报错都补一句这是哪个接入的
  模型、怎么用它(factory.model_note;设置页写那一组模型、接入两栏的叫法),免得照「启用它」去启用那条旧接入;
- 保存设置 422:下拉框选的写「指定的」,不写「填写的」;去掉和设置页空选项对不上的留空半句;助手那一项叫「助手使用的
  模型」;核查的格式检查写「留空表示跟随证据裁判模型」,同设置页;
- 「交给助手」后只剩接入问题时的说明:一句一个意思、不再带冒号(界面上前面已有「助手:」),「核对名称」有宾语,不说
  「节点」;有一条带修复时才指到各条问题的修复;
- 助手的模型取不到时,提示指到真正能改的地方:「编排」页助手栏的「助手使用的模型」(设置页没有这一项);
- 节点点名了接入、没写模型时校验说「将使用接入「X」的默认模型」(编号 model.unset);X 本身不存在或已停用时画布校验、
  发布一类都去掉这一条,只留接入那条,不再一条说照常执行、一条说会失败。
2026-10-06 17:29:18 +08:00
ThearchyHelios 1136b2d09d K3:换了写法的号码在回答正文、质询工件的文字格和最后一道关里也认得出
验收准备时复现过:模型把号码写成 199-1474-2785、199 1474 2785、199.1474.2785、(199)1474-2785、+8619914742785、
全角数字,回答正文、事件、回答文档原样带着,最后一道关 _leaked 也拦不住,正式运行里永久封存。

engine/masking.py 的 _Secrets 新加 loose_spans:在正文里找允许夹分隔符的数字串(数字含全角,之间夹一到三个空格、
连字符、点、括号,含全角;开头可带 + 和括号),从每一截起往后拼,规范化(全角转半角、去分隔符、去 0086 / 86 国家码)
之后在「遮罩值的纯数字写法」集合里查——集合查找,不现编正则,两万个遮罩值照样不卡。拼出来的要是完整的号码:和别的
数字只隔着一个连字符或点连着的(13900000001-2、日期)、只有两截中间一个点的(小数)不算,同原来按词认的边界。
括号配成对:(199)1474-2785 整个换掉,「(电话 199-1474-2785)」两个括号留着。存的值本身带分隔符或国家码的,也按
纯数字写法认。

- scrub:正文(prose)缺省这样认,文字格(质询工件里夹着的、整格就是号码的)也认;SQL 里不认,199-1474-2785 在
  SQL 里是一个算式。
- protect:提问人换着写法写进问题的号码照样免遮;SQL 里照原样留着的阈值换了写法也只在正文里不遮。
- inquiry._leaked:组装之后还认得出换了写法的号码,整份不交。

反方审稿的理由正文目前没有按值遮这一步(理由里只引用反方自己的查询格子,查询结果在核查 agent 的工具边界先遮,见
下一个提交),这里不另加。补单元测试(18 种写法 × 三种存法、不误伤日期小数编号、文字格、最后一道关、提问人写的、
阈值、两万个值的耗时)和端到端测试(11 种写法写进回答都换成「已遮罩」,事件、回答文档里也没有;备注里夹着换了
写法的号码时回答整份不交、质询工件只换那一截)。去掉实现后这些用例都会变红。
2026-10-06 17:11:00 +08:00
ThearchyHelios 7f7b690307 upload_dates 直接取现行配方(current_recipe_id):发布、启用旧版本时它和当前快照、表结构一起换,不必再经快照和最后一期导入去找;补测试:修改配方把支付日期改成文字后只剩下单日期,回滚到旧版本两列又都是日期 2026-10-06 17:04:42 +08:00
ThearchyHelios f1e6716d84 compose_doc 的说明对上 K5:doc["source"] 是调用方交来的原文(报告节点交来前去掉了整篇外层围栏,见 report._unfenced),不再说是模型的原文;只改说明,文档字段和内容哈希不变 2026-10-06 17:01:45 +08:00
ThearchyHelios 3e02446542 出口契约:报告的正文仍写在代码块里、一句结论都没切出来时记出具缺口,不再完整出具
K5 让报告节点去掉整篇外层的 markdown 围栏,规格有意不去的写法(围栏外有前言、只有开头没有收尾、信息串写成 text)仍会让整段正文落在代码块里:结论句 0 句,裁判不调用、反方审稿记 no_claims,没挂依据的、证据相矛盾的都数出 0 句,正式运行照样完整出具,读的人以为结论都核对过。

- nodes/io.py 的 _fenced_body:文档里一句结论都没有,却有信息串为空 / markdown / md / text 的代码块,里面是成句的文字(一行里有汉字又有中文句读,或五个以上英文单词以句末标点收尾)或者写着引用标记,就是正文被藏进了代码块。信息串为空的代码块里也常是 SQL:看内容,代码注释行(--、//、/* */)里的中文句子不算;sql、python、json 这类信息串不看
- _claims 在结论句策略为 judge 或 require_citation(报告节点的或出具契约自己写的)时,碰到这样的报告记一条缺口 GAP_FENCED_BODY「报告「X」的正文写在代码块里,结论句未能核对」,出具降档。缺口不带核查节点的前缀,时间线写在「校验不完整」下、出具横幅归「校验未全部完成」(run/decode.ts 的 splitIssuanceGaps,报告标题像核查节点名的也一样,界面不用改)
- 行为不变:有正常结论句的、代码块是代码的、结论句策略为 off 的

测试(新文件 test_contract_fenced_body):认得出的几种(前言、没收尾、text、无信息串、大写 MD、英文、只有列表但写着引用)与认不出的几种(sql、python、json、无信息串的 SQL、SQL 注释里的中文句子、数字表、外面有结论句、没有代码块);正式运行(裁判 + 反方审稿 + 出具契约)里前言、没收尾、text 三种降档并只带这一条缺口,以前是完整出具;正常报告、整篇外层围栏被去掉的、带 SQL 代码块的照常完整出具;require_citation 写在报告节点或契约上同样记,off 不管。去掉挂钩、去掉 0 句前提、去掉注释行排除、去掉信息串过滤、去掉引用信号、去掉句读要求各做过一次,测试变红。
2026-10-06 17:01:44 +08:00
ThearchyHelios 8c76f91ca9 K3:分档标签列在派生来源里被同名的遮罩列挡住时不再按标签认(J2 第 3 轮复验的缺口)
SQLite 让派生来源里同名的几列都留着,外层按名字取的是排在最前面的那一列。以前认标签列只看起了这个名字的选出项,
SELECT vip_level AS 档 FROM (SELECT *, CASE WHEN amount > 1000 THEN 1 ELSE 0 END AS vip_level FROM sales) t 交回的是
真实等级,却被当成只装着标签:等级 1、2、3 撞上 THEN 1 ELSE 0 不收进遮罩值,之后 WHERE vip_level = 1 在查询清单、
事件、质询工件里都照原样封存。

按复核建议保守处理(engine/masking.py):
- _block_label_of(单支、多支的第一支):第一个同名项前面有 * 带得出这一列(_star_label 不是 _ABSENT),或者有没起
  别名、去掉括号和 COLLATE 后同名的项((vip_level)、vip_level COLLATE NOCASE,SQLite 给子查询的结果列起名时去掉
  它们),都不算只装着标签;
- _star_label:没写表名的 * 展开了数据源里的表(或 VALUES、函数这类说不清出哪些列的来源)为 None——* 把表里的同名列
  照样带出来,不像直接写列名那样会报列名有歧义。

补单元和端到端测试:* 在前、t.* 在前、CTE 里 * 在前、JOIN 子查询 * 在前、(col) / ((col)) / COLLATE 在前、多支的
第一支,以及标签排在 * 前面、* 只带出派生来源里的标签时照旧算标签列。三条规则各自去掉,对应的用例都会变红。
另把「SQLite 里带小数的取模先取整」写进已知局限。
2026-10-06 17:00:48 +08:00
ThearchyHelios 0772b42bfd K3 第一步:把认遮罩列、遮罩值的共用部分原样挪到 engine/masking.py
按 SQL 认出取自遮罩列的结果列(_derived_hidden 一套,含分档标签列 _label_columns)、按值认遮罩值(_Secrets)、
快照遮好的样子(_masked_view),连同它们用到的写死值的算法,从 engine/inquiry.py 原样挪到新的 engine/masking.py,
一个字没改。接下来核查 agent 的查询工具要在交给模型之前先遮,工具这一层不该反过来依赖审批质询的模块。

inquiry 照旧按原名导出这些名字(nodes/dual_check 按 inquiry._derived_hidden 引用,测试按 inquiry 的名字引用),
行为不变:质询、双盲复算、反方审稿的相关测试全过。
2026-10-06 16:55:29 +08:00
ThearchyHelios 50f15b40c4 合入期 5 前置修复 K1:点名的模型接入不存在或已停用时照实报错,不再悄悄换成演示模型——各条用到模型的路径照实失败或记为核查不可用;保存设置时 422、画布校验给警告、发布与发布前检查挡住并给修复候选(不指定模型、改用启用的接入),只写模型而提供它的接入已停用也查得出 2026-10-06 16:55:27 +08:00
ThearchyHelios 2cf329648d 上传表格的日期列按导入时配方里的逻辑类型认:双盲复算选了时间列不同的一边时,上传源也能写业务日期
J1 给业务日期加了类型门槛(两边的时间列都在表结构里声明为日期 / 时间类型才写),而上传表格导入时日期一律按文字保存、表结构里总是 TEXT,上传源因此永远写不了业务日期。导入时配方里其实记着每一列的逻辑类型(导入确认时界面上标的「日期」):列表块里类型为 DATE 的列、交叉表的日期轴。查过别处:构建回执里只有存储类型(列表的 DATE 列和文字列一样记 TEXT、role 都是 text),简单导入的回执只有 INTEGER / REAL / TEXT,所以只按配方认。

- dual_check_facts.recipe_dates:一份配方里逻辑类型是日期的列({表: [列]}),表和列按 recipe_types.derive_tables 推,和建表同一份;日期轴写进它各分段的表,列表合计行另存的表不带日期列;类型按 _DATED_TYPE 认,只有时刻的不算
- dual_check_facts.upload_dates:只读,按数据源当前快照找配方(source_versions.snapshot_recipe_id,期 3 之前的快照取最后一期导入的),和 schema_cache 出自同一个版本;手工源、简单导入、快照或配方记录不在、配方解析不了一律给空
- 节点的 _source_info 带上 upload_dates,放进候选:审批卡的预告(preview / preview_joint)和裁决后的真写认的是发起审批时读的同一份,逐字一致;旧的 checkpoint 里没有这一项,照旧不写
- _dated_problem:上传源里按文字保存的列在 upload_dates 里的算日期列(按表结构认出的表名、列名去对,大小写不论);不在的、读不出配方的照现在的原因说「来自上传的表格,表格里的日期一律按文字保存」;声明成别的类型的、只有时刻的照旧

测试:derive 一级补导入认作日期的写、只有一边是日期 / 读不出配方 / 记在别的表上的照旧说按文字保存、手工源不认 upload_dates、按表结构的名字对、preview_joint 同样认;新文件 test_dual_check_upload 经真实导入接口建上传源(暂存 → 改配方 → 试运行 → 提交、简单导入),配方里两列都是日期的审核人选盲算后业务日期记「支付日期」(人工确认、备注写运行),支付日期是文字的选哪边都不写、预告和跳过原因一字不差,配方记录改坏了的照旧不写;去掉认日期、去掉候选里的 upload_dates、去掉日期轴各做过一次,测试变红。
2026-10-06 16:54:30 +08:00
ThearchyHelios 0a690a599b 发布前修复点名的模型接入:按执行时的取法算修完落到哪里,只写了模型而接入已停用的也拦下
- 「不指定接入」以前只去掉接入、留下同一处写着的模型,复核又只查点名的接入:按模型名落回停用的接入,或者成员跟随节点、节点跟随全图默认,拿别家的接入去调这个模型。检查、发布都通过,正式运行在那一处照样失败
- 现在每个候选(不指定、改用某个启用的接入)先在副本上改,按执行器的取法(成员先看自己、再看节点、再看全图默认;节点看全图默认;都没写按模型名找;核查和裁判整组交给设置)算出改动波及的每一处用哪个接入、哪个模型。落到的接入不提供那一处的模型、按模型名落到停用接入或认不出的模型、落到内置演示模型的,连同一级写着的模型一并去掉再算,还落不稳的候选不给;候选说明写清执行时改用哪里、模型去没去掉、换上的接入实际用哪个模型
- 门禁把哪一级都没写接入、只写了模型而提供它的接入已停用的那一处也记成 provider.disabled(落在写模型的字段,原因和运行时同一句):复核拒得掉这类修复,检查器里只能选模型的节点发布前也查得出来;这一处配「不指定模型」或改用一个启用接入的修复
- 按模型名找接入、没写时退回哪个接入抽成 factory.provider_for_model、fallback_provider,运行时和门禁共用一份
- 问题面板、修复预览写得出「模型」「成员「x」的模型」;check-publish 的伪造门禁跟上新的候选,补只写模型的一段
- 测试:复核发现的几种形状逐一钉住(含发布前检查修好、保存、发布后正式运行成功的端到端一条);依赖旧做法的两条测试改成新的正确行为
2026-10-06 16:31:06 +08:00
ThearchyHelios 3f17fd3c71 合入期 5 前置修复 K4:运行把这一段执行的截止时刻交给节点(engine/deadline.py),反方审稿与双盲复算开始每一项前看剩余时间,按剩余时间收紧每项时长、给下游留出余量,来不及的记为未能反查 / 无法复算并写明「运行时限将至」,整次运行不再被时长上限中止;继续运行取 checkpoint 里的结果不重复反查 2026-10-06 16:26:45 +08:00
ThearchyHelios 4314a7fc44 合入期 5 前置修复 K2:核查与裁判模型的输出额度提到 16384,服务方嫌大时退回 4096(和助手共用 providers/budget.py);每次调用认截断,因推理占满额度没交出结果时照实说(verify_truncated、judge_truncated),不再说成盲算没查到、没按结构交结果 2026-10-06 16:26:37 +08:00
ThearchyHelios 54ab06d615 合入期 5 前置修复 K5:写作模型把整篇报告包在 markdown 代码块里时去掉外层围栏(重写的几轮同样处理),结论句照常可审、裁判和反方审稿有句子可看;别的语言的代码块、围栏外有文字、多个代码块、开闭不配对的原样不动 2026-10-06 16:26:37 +08:00
ThearchyHelios 39068c9636 合入期 4 整合 J2:审批质询遮罩收口——阈值只在同一个数在别处确实被换掉时才遮、夹住某一个值的边界按条件与作用域认、质询工件里未遮罩文字格夹着的遮罩值按值换掉;算式边界、分档标签列、CAST 类型参数等变体照实处理,已知局限写进模块说明 2026-10-06 16:23:26 +08:00
ThearchyHelios 76b485a0ca 审批质询遮罩收口(第 3 轮复核):标签列按作用域认取自哪个来源,UNION 另一支、子查询里的遮罩列流进标签列照收;取模、整除、整数相除按算出来的值认;CAST 的类型参数不当边界换
复核留下的一条 major(第 2 轮只修了一部分)和两条次要问题:

- 分档标签列:以前按名字认,从「所有名字都只装着标签」起往下剔。遮罩列原样选出(SELECT vip_level)时名字就是它自己,
  永远剔不掉;结果的 UNION 按位置那一遍只往里加标签,另一支取的是遮罩列也不剔;派生来源里的 UNION 根本不按位置看。
  于是标签在前、遮罩列在后(不起别名、别名不同、外层再套子查询、CTE 里的标签再 UNION),遮罩列经按原名选出的子查询、
  以标签的别名传出,或者两个子查询 JOIN 起来同名,等级 1 撞上 THEN 1 都不收,WHERE vip_level = 1 在查询清单、事件、
  质询工件里照原样封存(对基线是回退)。现在 _label_columns 按作用域认每一项取自哪个来源:结果的这一列在 UNION 的
  每一支里都要是结果全为写死常量的 CASE / IIF,或者取自子查询、CTE、LATERAL 里只装着标签的一列(派生来源里的 UNION
  也按位置看每一支,写了列名表的按列名表);取自数据源里的表的一律不算,哪怕和标签同名。没写表名的列,派生来源出
  这一列的就是它(SQL 执行过,别的来源就没有同名的列,不然报列名有歧义),按这一列 USING、NATURAL 合成的除外;递归的
  CTE、UNION 里落在某一支 * 里的列认不准,照收(多遮)
- 取模、整除:_arithmetic、_worked_out 补上 %、MOD、DIV、//(往零取整,同 SQL)。顺带 / 照数据源的算法算:SQLite、
  Postgres 里两个写成整数的数相除只留整数部分(sqlglot 记在 typed 上),以前一律按小数算,age >= 25 AND age < 53 / 2
  在 SQLite 里夹住了 25 却认不出;写成小数的(53.0 / 2)、MySQL、DuckDB 照小数算
- CAST 的类型参数(第 2 轮引入,只多遮):_halves 只收往上走得到这一边的字面量(_lifts),CAST(30000 AS DECIMAL(10, 2))
  的 10、2 不再当成算出边界的数换掉,SQL 检查结果的片段里别处的 10、2 也不跟着换;_wraps 里 CAST 只算被转换的那个值,
  Oracle 的 CAST … DEFAULT 0 ON CONVERSION ERROR 里的 0 照原样
- 模块说明的已知局限照实更新:标签列认得出、认不准的几种写法;认得出的算式补上取模、整除和整数相除,认不出的补上
  位运算、POWER、CAST 改了值的

测试:单元补标签在前、遮罩列在后的八种写法(不起别名、别名不同、子查询里的 UNION、写了列名表的 CTE、CTE 里的标签再
UNION、经按原名选出的子查询传出、两个子查询 JOIN 同名、RIGHT JOIN USING 同名列),按作用域仍认得出的五种标签(外层连上
数据源里的表不写表名、按别的列 USING、两层子查询写了表名、UNION 两支、派生来源的 UNION 经 * 带出);取模、整除、整数
相除的六种写法和照小数算的三种;CAST 的类型参数和 DEFAULT。端到端补 UNION 标签在前的两种写法、取模和整数相除算出来的
边界。每处实现都逐个去掉验证过测试变红(十一个变异全红)
2026-10-06 16:01:28 +08:00
ThearchyHelios 3c51c40e9d 点名的模型接入:发布前检查给每一处配修复,提示指到界面上真能改的地方,复核发现的几处漏查、误查补齐
多数节点(模型调用、Agent、报告撰写、多 Agent 协作、条件分支、结构校验、知识检索)、协作成员和全图默认的接入在检查器里没有下拉框,以前点名的接入坏了只能去设置里建一个同名接入。现在门禁接受接入表(governance.lint_for_publish 的 providers,接口层读好传进来,同数据目录 SQL 检查器),点名接入的问题成了门禁的一部分,自动修复给每一处一条要人选的修复:改用一个启用的接入,或不指定接入;选的接入不提供这一处写着的模型时模型一并去掉。一处一条(修复 id 带字段,复核只认那个字段),协作节点自己的接入和成员的接入各修各的。校验和发布检查的提示按位置写:有下拉框的说「在节点中换一个接入」,别处说「在发布前检查中改用其他接入」;剩下的只有接入问题时交给助手的说明也改成指向这些修复。

复核发现一并修:核查节点自己的开关关着、结构校验不许重试时不再查(执行时根本不取模型,以前会挡住发布);报告没写裁判配置时查全图默认里的那一份(以前放行,正式运行却全部未裁判);接入名不是字符串时查助手模型的接口不再 500,设置接口对门控、助手两组的这类值回 422 带字段;助手、表格导入、数据目录起草用的模型点名的接入不存在或已停用时照实说是哪个接入、去哪里改(以前说「尚未配置完成」,停用接入的下一步丢了),删掉两处已走不到的分支。

测试补齐:结构校验让模型修复、知识检索让模型重排、按需裁判各路径的不存在与已停用,助手、定时正式运行、裁判的两种情况,反方审稿节点上点名的接入;修复的候选、应用、拒收和图级默认。
2026-10-06 15:14:26 +08:00
ThearchyHelios 5d4d1fdc15 审批质询遮罩收口(第 2 轮复核):算式边界、套了 CAST / 汇总的列、换个写法的数、两头不是整数、范围再排除一头都认得出;只装着标签的列才不收标签;截断片段整段去掉
复核留下的三条 major 和两条次要问题:

- 范围的一头是算式(age <= 25 + 0、buyer_phone BETWEEN 13900000000 + 1 AND 13900000009 - 8):夹住的值认得出,算式里的数
  却照原样留着,一算就是那个号码。现在几个写死的数的加减乘除按算出来的那一个值认(_lifts):夹住某一个值的边界、等值、
  IN 里的算式,里面的数都换;选出列里算出来的号码按值换;没夹住的算式阈值和写成一个数的阈值一样照原样,正文里复述它
  (连同算出来的值)不遮,算出来的值在别处换掉了也不留。原有两条用例(BETWEEN 13900000001 AND 13900000002 - 1、
  … AND 13900000001 + 0)的期望随之改成算式里的数都换(多换一个 1、0,号码照样不露)
- 几种找值写法以前认不出:被比较的一边套了 CAST、汇总(CAST(age AS INTEGER) = 25、HAVING MAX(age) BETWEEN 25 AND 25)
  改按它的值取自哪些列认(_columns_of);同一个数换个写法(25.0、025、2.5e1、013900000001)按规范写法和遮罩值比
  (_canonical);两头不是整数的范围(age > 24.5 AND age < 25.5、BETWEEN 25 AND 25.9)在整数上隔开紧挨着的两个整数;
  范围再用不等、NOT IN 去掉一头(buyer_phone >= 13900000001 AND buyer_phone <= 13900000002 AND buyer_phone <> 13900000002,
  以前号码被当成阈值、回答正文跟着不遮):等值一类只在同一个条件里和范围合起来看,整数上紧挨着的被排除的数越过去接着
  看(age <= 30 时 27 照样成立,不算夹住),跨条件配对仍只看范围比较,不等、IN 单拿出来不当「写出了这个值」
- 算式求值改成不递归(_constants):visits <= 1+1+…写到 1500 项时以前超过递归深度,整条 SQL 退回按词认,同一句里夹住的
  年龄照原样封存
- 分档标签不收进遮罩值的规则太宽(第 1 轮引入):改了名字的遮罩列(vip_level AS 等级)的 1、2、3 撞上同一条 SQL 里按
  条件数个数的 THEN 1 ELSE 0,1 就不算遮罩值,WHERE vip_level = 1 照原样封存。现在只跳过只装着标签的列的格子
  (_label_columns:选出项本身是结果全为常量的 CASE / IIF,或者经别名、子查询、CTE 原样传下去的,没起别名的按位置认;
  同一个名字在哪一处装着别的就照收)
- SQL 检查结果的说明:码值表里的码值和别的字一样按值换,说明照实写(多遮,不漏),补测试钉住
- 截断的片段停在带空格的遮罩文字值中间(地址):以前只去掉最后一段,前半截还留着;现在从没闭合的引号、注释起整个去掉
- 模块说明的已知局限照实更新:两个产品取舍保持;夹住的认法补上不等、NOT IN、两头不是整数和越过排除数的规则;新增一条
  列出认得出的找值写法和仍认不出的(值藏进和列一起算的算式、拼接、函数拼出来的值)

测试:单元补算式边界、套了 CAST / 汇总、换个写法、两头不是整数、排除一头、不该夹住的几种(范围加不等仍不只一个值、
IN 列表旁的阈值、选出列里的等值和 WHERE 的范围)、上千项连加、改了名字的遮罩列撞上标签、标签列经子查询 / CTE / 没起
别名、截断片段、码值表;端到端补长号码的五种写法(算出来的边界、算出来的等值、两头不是整数、范围再排除一头、补零)、
年龄的六种写法和改了名字的等级列。每处实现都逐个去掉验证过测试变红
2026-10-06 15:08:56 +08:00
ThearchyHelios 48f656f362 证据裁判截断的回复整批不采信,截断另记 judge_truncated,时间线的下一步指到换模型
复核第 1 轮的发现:
- 有的网关把截断的强制工具调用解析成半截清单:第一条判定写完、第二条只写了编号。_ask 拿到清单就交出去,不再看截断,于是记成一句支持、一句格式不对、两句未给出判定,llm.end 也不标截断。现在回复被截断就整批记 truncated(最后一条可能只写了一半,也可能判定写完、理由写到一半,分不清哪条完整),纯文本调用同理
- 截断的那一批原来只发 llm.end(时间线标题「结论句裁判未能执行」)和 judge_failed(下一步「检查裁判模型的接入后重新运行」):截断时接入是好的,同一个模型重新运行多半还是截断。现在另发 judge_truncated(max_tokens、unjudged),judge_failed 不再带截断;时间线上调用那一行说「裁判模型的输出达到上限(N token)被截断」,judge_truncated 那一行的下一步指到在报告撰写节点的「结论句裁判」或设置里换不带推理的裁判模型,入口落到节点的结论句裁判;check-decode 加一节
- 复核层(review.py)认得 judge_truncated:是缺口,记成它自己的 kind
- 退回小额度按 budget_refused 认,和核查同一种记法记在进程里:之后每份报告、每次点击直接用小的
测试补上半截清单(剧本和真实 ChatAnthropic)、纯文本清单后面还在写、截断和格式不对各记各的日志、跨报告记住被嫌大、自建服务的 max_new_tokens
2026-10-06 14:30:15 +08:00
ThearchyHelios 4905de728c 核查截断的回复一律不采信,触顶原因的先后照旧,截断的建议按模型从哪来的说
复核第 1 轮的发现(主要一条):Anthropic 的工具调用写到一半到了上限时,回复照样带着 tool_use(input 是空的,有的网关给半截对象)。工具循环只在没有工具调用时才看截断,于是一轮轮执行空参数的查询,撞到步数上限,最后记成「没有按结构交结果」;收尾的强制工具调用截在半路时解析出空对象或半截对象,空对象记成没按结构交,反方审稿的半截对象(结论写完、理由写到一半)被当成「经受住」,质询把半句话当成回答。
- 工具循环:回复被截断就停,带着的工具调用不执行(半截的 SQL 查出来的数是错的),也不收尾
- 截断前查到过东西的也不再按查到的收尾:截断前查到的不一定是要的那个数,交出去的结果只在 notes 里写一句「可能不完整」,审批卡、反方结论、质询回答都看不到,残缺的结果还照常参与比对和处置。同时长到点一样算半路被掐断,原因照截断说,查过的照记;TRUNCATED_SETTLED 和日志里的 delivered 字段去掉
- 收尾:回复被截断时,解析出来的、从回复里抠出来的都不采信(结构化、普通调用都是),也不退回普通调用
- 上一轮的 give_up 把触顶原因的先后从「先 stopped」换成了「先 refused」:去掉 give_up,收尾被上限拦下时照旧报先撞上的那个(双盲复算按它写无法复算的原因,反方审稿按每日上限决定还派不派后面的句子)
- 截断的原因开头只说「未能给出结果」(质询里提问人看到的就是这句,不说「核查未能完成」);建议在节点里说「在节点上指定不带推理的模型,或在设置里更换核查模型」(节点上的模型优先于设置,和时间线上的下一步一致),质询只说设置
- 退回小额度按 budget_refused 认,按 (接入, 模型, 额度) 记在进程里
- 时间线:verify_truncated 的副标题固定说「这次核查未能给出结果」,check-decode 跟上;review.py 里 verify_truncated 的注释跟上
测试补上真实 ChatAnthropic(只有 HTTP 层是假的)的循环截断、收尾截断(空对象、半截对象),真实 ChatOpenAI 写到一半的工具调用参数,Bearer 鉴权下 SDK 在本地拒绝时退回 4096,自建服务的 max_new_tokens,跨次核查记住被嫌大,触顶原因的先后
2026-10-06 14:30:03 +08:00
ThearchyHelios e3f5d566d0 核查因运行时长上限将至没核成的,时间线另起一条并带上原因(cause: run_deadline);收紧过的核查 agent 触顶时也改说时长上限将至;给下游留的余量看下游还有几个要调模型的节点,时长上限调短时余量跟着缩小
为什么(复核第 1 轮):没开始的句子、指标和收紧后到点的,以前和别的原因记在同一条日志里,没有可机读的字段,界面只能照旧叫人调高每句、每项的上限(调高了只会更快撞线),或去查核查模型和数据源(它们本来就没问题);前面有一句因别的原因没审时,运行时长上限这个原因还被藏进「等」里。双盲复算只给下游留 60 秒,惯常接法「双盲复算 → 写报告 → 反方审稿 → 成果」里写报告一慢,运行照样在写报告时被中止;时长上限调到 180 秒左右时,固定的 60 秒余量又让核查一项都开始不了。

- 反方审稿:没开始的句子另发一条 challenge_unreviewed,带 cause、run_seconds,不混进别的原因;收紧后到点的那条 challenge_limit 照记 timeout_s(老界面认得),另带 cause、run_seconds 和触顶的句数 claims,同一节点里有句子触了自己的上限时两条分得开。产出的 unreviewed 照算
- 双盲复算:没开始、收紧后到点的盲算结果带 cause、run_seconds,_compare 记无法复算时一并带上(只这一行),dual_check_unchecked 按有无 cause 分两条发,时限将至的那条另带 cause、run_seconds,原话列指标名(最多 5 个)
- deadline.relabel 包住交给核查 agent 的 emit:收紧过的那一项到点时,verify_limit 改说「本次运行即将达到 600 秒的时长上限,时长已按剩余时间收紧到 52.3 秒」,带 cause;别的事件、别的上限原样转发,没收紧时原样交回
- 余量按份算:一份取 60 秒和这一段的 10% 中大的,这一段不到 240 秒时按 25%;下游这一段里(到人工审批为止)每个还要调模型的节点(模型、Agent、协作、子工作流、写报告、双盲复算、反方审稿)各再加一份,合计不超过这一段的一半。默认 600 秒的一段、下游只有成果节点时和以前一样
- 没有截止时刻时一切照旧;决定都还在原来的 @task 里,接着运行取 checkpoint

测试:反方审稿、双盲复算各补一条「另一个原因 + 时限将至」两条分开说、带不带 cause 的用例,反方审稿补一条触了自己上限和收紧后到点分得开的用例;原有用例补上 cause、run_seconds、原话的断言。假的钟钉住下游有写报告时第 3 项只查 40~50 秒(没有时 100~110 秒);真的钟用 10 秒一段、盲算查到时长用完、写报告要 3 秒,以前在写报告时被中止,现在运行成功。单元测试钉住短时长的余量、下游每个节点加一份和一半的上限、数下游节点(人工审批之后不算、汇合只算一次、环回自己不算)、relabel 改什么不改什么。去掉下游余量、去掉 relabel 后对应用例都变红。
2026-10-06 14:25:38 +08:00
ThearchyHelios dde0927460 审批质询遮罩收口(第 1 轮复核):「夹住某一个值」在整条 SQL 里认,只有分档的几种写法各算各的;分档标签不当遮罩值;SQL 检查结果的片段、说明照整条 SQL 换
- 夹住:上一轮只在同一个条件里配对,把找值拆到 ON 和 WHERE、WHERE 和 HAVING、INTERSECT / EXCEPT 两支、包在 CASE / 函数 / 算式里、子查询和 CTE 里,完整手机号就照原样封存、回答正文也跟着不遮。现在整条 SQL 里配对,只有 CASE 的不同分支、IIF 的真假两支、SELECT 里不筛行的不同项(选出列、GROUP BY、ORDER BY)、UNION 的不同支各算各的(_apart,看最近公共祖先);同一个条件里照旧按合起来的真假细看。整数上 <= v、>= v 也隔开相邻的整数(age <= 24 OR age >= 26 就是 age <> 25);BETWEEN v AND v、>= v AND <= v、> v OR < v 条件里哪一层这样写都算(被更宽的范围吸收、旁边有矛盾的条件、隔着别的条件写也认得出),整数的半开区间仍只看整个条件,成百个相邻分档照旧不遮。条件的各层摊开一次反复用,600 档的耗时和上一轮相当
- 分档标签:CASE / IIF 结果全是写死常量的,标签文字是 agent 写在 SQL 里的,不收进遮罩值,规格原句的 SQL、回答里的档名照原样;当分组键(每一档一行)或套在汇总里按条件数个数(SUM(CASE … THEN 1 ELSE 0 END))不算派生值,格子照常显示,分得清哪一档几笔;逐行的标签列、汇总遮罩列本身的值照旧整列遮
- 质询工件的 SQL 检查结果:片段是检查器重新写出的表达式,单拿出来认不出在找哪一个值,改为按整条 SQL 里整个换掉的字面量换(简单 CASE、行值、BETWEEN v AND v 里的短的数也换掉,阈值照原样);说明里照引的取值(「没有码值「3」」)一样换;截断处半截的号码去掉
- 同一个短的数在别处换掉时,别的列上的阈值、LIMIT 照原样:一律换会回退第 2 轮起的「age = 25 AND amount > 25 … LIMIT 25」,写进已知局限并补测试钉住
- 模块说明的已知局限照实改写(分档拆进两个 CTE / FROM 子查询、同一选出列里两档汇总相除会多遮,拆进 CASE 分支 / 不同选出列认不出,运行详情的工件页签不经这里的遮罩)
- 测试:端到端改回规格原句(文字档位、SUM(CASE …)、年龄段 CASE),补 ON/WHERE、WHERE/HAVING、INTERSECT、包在 CASE 里、CTE、相邻整数的端到端用例,单元补拆分写法、被吸收的 BETWEEN v AND v、分组标签、检查结果片段与说明;每处去掉实现都验证过变红
2026-10-06 14:05:52 +08:00
ThearchyHelios a7b8854dc4 双盲复算复核修正(第 1 轮):业务日期只认带日期的类型、原因按哪一边说清;遮罩列在子查询或 WITH 里改了名不再收窄,交给盲算的分组取值按 SQL 认出取自遮罩列的;按位置取的格点名原因、认不出出处的不写进计算式、不再改变容器输入的判法
业务日期的类型门槛:只有时刻的 TIME / TIMETZ 不再算(一天里的几点几分不是日期,MySQL 还常拿它存时长;和 catalog.is_date_column 同一个口径,记成业务日期后 SQL 检查会对按真实日期列统计的查询一直报错误级),原因说「只有时刻,不含日期」。类型照表结构里记的说「类型是 X」(introspect 存的是 SQLAlchemy 反射出来的类型,SQLite 里写成 TIMESTAMPTZ 的反射成 NUMERIC),没写类型的列反射成「NULL」,按没有声明类型说。被采纳的一边有问题说「被采纳的盲算按时间筛的列……」,未被采纳的一边另说清为什么也要看它(两边都是日期列时,审核人选的才是业务日期按哪一列)。数据源还没有可用的表结构(未探查、探查失败)时说表结构不可用,不说这一列不在表结构里。上传表格建的数据源(origin=upload,节点认出后经候选的 uploaded 交给 derive)日期一律存成文字,原因说「来自上传的表格,表格里的日期一律按文字保存」,不说成用户声明了 TEXT;预告和真写逐字一致。

遮罩:收窄补等值条件、交给盲算的分组取值,都按 SQL 认出取值来自遮罩列的名字(复用审批质询的 inquiry._derived_hidden:起别名、套函数、经子查询或 WITH 转一道、写在列名表里的都认,解析不了又写着遮罩列的整份算)。以前外层按 area 分组、area 在子查询里由 region 改名而来时照常补上「area = '华东'」,差异说明还原成「region = '华东'」进了审批卡、对照工件和目录日志,「华东」也进了盲算的任务;和遮罩列无关的 WITH 照常收窄。按位置取自遮罩列的输入直接交回「输入 vars.res.rows[0][0] 取自设置了遮罩的列,不能用于复算」(同 cell() 的说法),「核对不了」改成「无法核对」。伸进查询结果的行、却认不出取自哪一列的(下标不是字面量、改过形状的结果没带快照 id 和数据源名)计算式里写成「其他输入」,不把卡上的值交给盲算。按位置取的格落在的快照只为认遮罩算遮罩,不再进重算的快照清单:以前同一个指标里装着它整份行的容器输入会对上它、被它的遮罩列挡下。
2026-10-06 13:42:51 +08:00
ThearchyHelios 9f0e453884 整篇包在 ``markdown 里、中间再套一段一样长的 ``sql 时照样去掉外层围栏:带信息串的一行开、不带的一行收,两两配对。以前一样长的内层围栏一律不去,模型最常见的这种写法仍是 0 句结论,裁判和反方审稿没有句子可审,正式运行照样出具
规则:中间和外层同种符号的围栏行,比外层短的跳过,比外层长的不去(规格原文),一样长的按配对看——带信息串的开一个代码块,不带的收掉它;没有可收的 ``` 说明外层在这里就收了(好几个代码块),开着时又来一个 ```sql、扫到最后还开着的,都不去。去掉外层后 compose_doc 和前端每见到一行 ``` 开合一次,配好的一对正好切成一个代码块,原 docstring「见到 ``` 就收」的理由不成立,改准确了。

一样长的行只认 compose_doc 和前端认得的写法(信息串是紧跟着的一个词):``` sql 这种它们不认,算一行正文。不然它会和后面的 ``` 配成一对、去掉外层,而后面那个 ``` 在它们眼里成了开头,把结论句吞进代码块。比外层长的照旧按行首判,````sql title 这样的也不去。

外层反引号里套 ~~~、四个以上反引号的代码块照常去掉外层:compose_doc 和前端只认 ``` 围栏,这段按正文核对,和模型不包外层、直接这么写时一样,比整篇一个代码块强;docstring 写明,测试钉住。

测试:same-fence-inside 挪到去的一边,补多段内层代码块、~~~ 里套 ~~~、反引号里套 ~~~;不去的补没有可收的 ```、开着时又来 ```python、内层没收、``` sql 开头、更长的带词围栏。compose_doc 一级补 ```markdown 套 ```sql(SQL 仍是代码块,结论句 2 句)和 ~~~ 块按正文核对。端到端(正式运行,裁判 + 反方审稿 + 出具契约)加一组套 ```sql 的写法:两句都判、都审,完整出具。裁判退回改写(rewrite_once)那一稿也包在代码块里时照样去掉:改写稿被采用,第二轮判到改过的那句,以前这条路没有测试守着。每条判断各做过一次去掉、测试变红。
2026-10-06 13:32:38 +08:00
ThearchyHelios 3bb06e32f1 整理:核查收尾时一条报错的续行对齐,裁判的 _TRUNCATED 挪到用它的 _ask 前面 2026-10-06 13:13:49 +08:00
ThearchyHelios d9acfbfc0b 收紧后的时长取到 0.1 秒、往下取:核查 agent 触顶时时间线写「时长上限 69.7 秒」,不写 69.7012 这样一串小数;往下取不会超出算好的余量,差一点不够的照样不开始
另把 segment、allot 的说明补全:deadline 为 None 时放的是 None(里面看不到外面的截止时刻),配置的是不限时按收尾宽限算最短时长。
2026-10-06 13:07:23 +08:00
ThearchyHelios 2c2672cc04 时间线认得核查模型的输出被截断(verify_truncated):标题说「核查模型的输出达到上限(N token)被截断」,副标题说这次核查未能完成或已按截断前查到的结果收尾,下一步指到换不带推理的模型,入口落到节点的模型
decode.ts 按结构化字段(max_tokens、delivered)解读,不从原话里认,缺字段时只给不带数的标题;文案放在 terms.ts 新的 VERIFY_TRUNCATED_TEXT。check-decode 加一节:先核对 verify_agent.py 还是这么发的,再核对三种取值(没交出结果、退回 4096 后交了结果、缺字段)的标题、副标题、下一步、入口和原话。

跑完之后的复核层(engine/review.py 的 WARN_CODES)登记 verify_truncated:同 verify_limit 记成它自己的 kind、degraded,不落成笼统的 warn,也不触发「重跑一次大概率能好」。
2026-10-06 13:06:18 +08:00
ThearchyHelios cb3f20d6df 保存设置、画布校验、发布时就查出点名了不存在或已停用的模型接入
运行时点名的接入不存在或已停用现在照实报错,这里把问题提前到配置的时候:

- PUT /api/settings:门控模型、证据裁判、核查模型、助手这几项引用接入的字段写了不存在或已停用的接入
  回 422 {detail, code: settings_provider_invalid, field: 组.键},说明写设置页上的叫法、和运行时报的
  原因提示同一句;一组出错整次不落库,留空照旧表示沿用上一级。只查这次改了的值:写好之后接入才被删、
  停用的,不挡这次保存同组别的项(设置页标着不存在,运行时照实报错),同设置页只校验改过的做法。
- 画布校验给警告,发布、发布前检查、自动修复的复核给错误挡住发布,问题落在节点和字段上(provider、
  agents[i].provider、judge.provider,全图默认记 defaults.provider),编号 provider.missing /
  provider.disabled。只查执行时真会取模型的那几处:分支让模型分类、校验让模型修复、检索让模型重排时
  才算,报告交给模型裁判时才查裁判的接入,有节点会取全图默认时才查默认。判断在 engine/provider_refs.py,
  接入表由接口这一层读出来交给它(门禁本身不查库,同 SQL 检查器)。自动修复后只剩接入问题、人又要
  交给助手时说清助手处理不了。

test_verify_settings 的整组往返原来写了一个不存在的接入 house,改成先建一个演示类的接入。
2026-10-06 13:04:50 +08:00
ThearchyHelios 4a3de10d38 证据裁判按 16384 的额度判、服务方嫌大时退回 4096,判定写到一半到了上限的记未裁判(truncated),照实说是输出额度
裁判原来固定 4096、thinking off;经 OpenAI 兼容接入的推理模型收不到 thinking=off,思考能把 4096 用光,结构化调用写不出判定、退回纯文本也一样,最后记成「裁判返回的结果格式有误」。现在:

- 额度 MAX_TOKENS(16384),服务方嫌大(400/422 且报错提到 max_tokens 一类)时退回 FALLBACK_MAX_TOKENS(4096)再问一次,之后各批都用小的、只撞一次;小额度的模型要退回时才建
- 每次调用看有没有被截断:Anthropic 的 stop_reason=max_tokens、OpenAI 系的 finish_reason=length、openai 结构化输出抛的 LengthFinishReasonError。截断的这一批记未裁判,原因是新的 truncated(和格式不对分开记:说成「格式有误」人会去查提示词,该换的是模型),理由写「裁判模型的输出达到上限(N token)被截断,这句未裁判。通常是推理占满了输出额度,可换用不带推理的裁判模型」,缺口、llm.end 的 error(带 truncated、max_tokens)、judge_failed 日志同样写明
- 结构化调用被截断的不再退回纯文本多花一次额度;截断那次的用量从异常里取,照记账
- 别的批照常问(同格式不对);没有截断时原有说法不变

测试:新增 test_judge_truncation(结构化调用截断、LengthFinishReasonError、纯文本只有思考、一批截断不影响别的批、退回 4096 只撞一次、退回后截断写 4096、格式不对的说法不变)。
2026-10-06 13:02:24 +08:00
ThearchyHelios b54545b5d4 双盲复算在运行时长上限内收尾:每项盲算开始之前看这一段执行还剩多少时间,不够就不再开始、按无法复算记缺口,够的话时长上限按剩余时间收紧;运行不被中止
为什么:盲算最多 3 路并行、每项 120 秒再加 30 秒收尾宽限,指标多了一样能耗光一段执行的时长上限,整段盲算没进 checkpoint,「继续运行」全部重做、再花一次钱。

- 只动安排盲算的 _blind:开始一项之前问 deadline.allot。不够时交回一个没有模型的结果(原因「本次运行即将达到 600 秒的时长上限,为保证运行按时完成,未进行盲算」),走现有的路径:_compare 记无法复算、dual_check_unchecked 点名、出具缺口照记;第一项就不够时和核查模型用不了一样,后面的不再一个个去试
- 时长收紧过的盲算到了点:照样按无法复算记(同触了每项时长上限),原因说运行时长上限将至、盲算时长已按剩余时间收紧;不留 limit,不发 dual_check_limit——它叫人调高每项的上限,调高了只会更快撞线
- 决定都在 dual_check_blind 这个 @task 里:审批恢复后重放取 checkpoint 里的结果,不再盲算,哪几项没来得及照旧
- 时间充裕时时长上限照传原值,和以前一样;_plan_metric 等别处没动

测试:假的钟钉住五个指标只够盲算 2 项、第一项都来不及、收紧的那项到点怎么说、审批恢复后不重做;真的钟用 5 秒的时长上限、一项接一项的慢盲算,以前被中止,现在运行成功。去掉判断后这几条都变红。
2026-10-06 13:01:20 +08:00
ThearchyHelios 4a3899f4d5 反方审稿在运行时长上限内收尾:每句派出去之前看这一段执行还剩多少时间,不够再审一句就停下,这句和后面的记为未能反查,节点照常收尾、运行不被中止;够的话这句的时长上限按剩余时间收紧
为什么:逐句串行反查(为了时间线看得出哪条查询在审哪一句、每日余额扣得确定,这个设计保留)默认 5 句、每句 90 秒再加 30 秒收尾宽限,最坏正好是一段执行的 600 秒,加上取数、写报告必然被中止;中止时反查的 @task 没做完,「继续运行」从头再审、再次超时。

- 判断用 deadline.allot(给下游留余量、扣收尾宽限),只在要派 agent 的句子上做:没有可查数据源这类不花时间的句子照记原来的原因。一旦停下,后面的句子照同一个原因记(同每日金额用完、核查模型用不了的处理),理由写「本次运行即将达到 600 秒的时长上限,为保证运行按时完成,这句未反查」,challenge_unreviewed 的原因写「……剩下 N 句未审」,产出的 unreviewed 照算进去
- 时长收紧过的句子到点按查到的结果收尾,challenge_limit 照记 timeout_s(界面认得),说法另起:说运行时长上限将至、反查时长已按剩余时间收紧,不说「每句时长上限 90 秒」——那会叫人调大每句的上限,调大了只会更快撞线
- 决定都在原有的 @task 里做,输入指纹不含截止时刻:接着运行时取 checkpoint 里的审稿,不重审,没来得及的照旧
- 时间充裕时时长上限照传原值,和以前逐字一样(现有用例全过)

测试:假的钟(每派出一句拨钟若干秒)钉住 5 句只够审 2 句、收紧的那句触顶怎么说、一句都来不及、接着运行不重审;真的钟用 7 秒的时长上限和查不完的慢 agent,以前被中止,现在运行成功、计数对得上。去掉判断后这几条都变红。
2026-10-06 13:00:48 +08:00
ThearchyHelios a1d5dcd0ac runner 把这一段执行的截止时刻交给节点:进入 asyncio.timeout(max_run_seconds) 时放进 ContextVar(engine/deadline.py),退出时清掉,节点、节点里的 @task、子工作流都看得到;没有上限时为 None,节点照旧
为什么:反方审稿逐句串行反查、双盲复算按项盲算,一个核查节点就能把一段执行的 600 秒用光,整次运行被中止,装着反查、盲算的 @task 没进 checkpoint,「继续运行」从头再来一遍、再次超时。核查节点要先知道还剩多少时间,才能在上限之内收尾。

deadline.allot(时长上限, grace=收尾宽限) 给核查节点在开始每一项之前问:先给下游留出余量(60 秒或这一段总时长的 10%,取大的:60 秒够成果节点复核、再做一两次普通的模型调用;时长上限调大了的,节点多半本来就慢,余量跟着放大),再扣掉收尾宽限,剩下的比配置的时长短就收紧到它;收紧之后不到 min(配置的时长, 收尾宽限)(一次模型调用的时间)就不再开始。不经 runner 执行时原样放行、时长上限还是传进来的那个值。

runner 只改一行:async with deadline, segment(deadline.when(), max_run_seconds),截止时刻就是 asyncio.timeout 的那一个、同一个钟。测试钉住 allot 的各档、余量随总时长放大、退出(含出错)时清掉,以及节点和节点里的 @task 都看到同一个截止时刻、审批恢复后的那一段重新计时。
2026-10-06 12:59:49 +08:00
ThearchyHelios a552f4feab 核查模型的输出额度改为 16384、服务方嫌大时退回 4096,截断照实说:工具循环、收尾的每次调用都看有没有到了上限被截断
额度:judge.MAX_TOKENS 从 4096 改为 16384(核查的 verify_model_spec 沿用它)。经 OpenAI 兼容接入的推理模型收不到 thinking=off,思考算在额度里,4096 常常想完就没额度发查询、交结果;16384 是常见网关接受的输出上限里偏大的一档,cite_fields 的抽取也是这个数。服务方嫌大(400/422 且报错提到 max_tokens 一类)时退回 judge.FALLBACK_MAX_TOKENS(4096)再试一次,工具循环和收尾共用一个开关,之后都用小的;小额度的模型要退回时才建。

截断:工具循环里没有工具调用、finish_reason=length(或 stop_reason=max_tokens)不再当成「模型说完了」,一条查询都没成功时不再收尾(收尾只能交「查不到」,双盲复算就会记成「盲算没有查到这个数」);查到过东西的照常收尾,取不到要的数时同样交回截断的原因,取得到的照常交、notes 里注明。收尾被截断(Anthropic 强制工具调用写到一半、openai 结构化输出抛 LengthFinishReasonError、普通调用只有思考)时交回截断的原因,不再退回普通调用多花一次额度;截断那次的用量从异常里取,照记账。给人看的原因写明「核查模型的输出达到上限(N token)被截断,核查未能完成。通常是推理占满了输出额度,可在「设置 → 偏好设置 → 核查」中换用不带推理的模型」,经 error 交给双盲复算的无法复算原因、反方审稿的「反查未完成」、质询的报错;节点里调用时另记一条 verify_truncated,llm.end 标出 truncated 和额度。没有截断时原有说法不变。

测试:新增 test_verify_truncation(假模型返回 finish_reason=length、只有思考、LengthFinishReasonError,覆盖工具循环、收尾结构化和普通调用、退回 4096、别的 400 不重试、三条渠道);三处钉着 4096 的断言改成 16384。
2026-10-06 12:58:37 +08:00
ThearchyHelios 0600144b73 写作模型把整篇报告包在一个代码块里时去掉外层围栏再核对:不去的话整篇在 compose_doc 里是一个代码块,裸数字照样标出,可一句结论都切不出来,证据裁判、反方审稿没有句子可审(反方审稿记 no_claims 直通),正式运行照样出具,界面上整篇显示成代码
报告节点的 compose 统一先过 _unfenced:第一稿、重写稿、裁判退回的改写稿都经过它。只在拿得准时去——去掉首尾空白后第一行是围栏(``` 或 ~~~)、信息串为空或 markdown / md(大小写不敏感),最后一行是同种符号、同样长的收尾围栏,中间没有同种、不比它短的围栏行;信息串是 sql、json 等别的语言、围栏外有前言结语、有多个代码块、开闭围栏对不上、里面嵌着同样长或更长的同种围栏的,原样交给 compose_doc(返回的就是原字符串,文档哈希不变)。正文自己的代码块套在更长的围栏或另一种符号里时照常去掉外层,里面的原样留下。doc["source"] 记去掉围栏之后的正文,markdown 由它渲染,出口契约复核取回的也是这份文档

测试:各种去与不去的写法逐条钉住(每条判断各做过一次去掉、测试变红);节点里裸数字照样标出、引用照常渲染、重写稿同样处理、别的代码块原样不动;端到端(虚构 SQLite 源)带围栏的报告里单元格引用照常解析,裁判两句都判、反方审稿两句都审,出口完整出具
2026-10-06 12:44:44 +08:00
ThearchyHelios 898bd8a276 审批质询遮罩收口:模块说明和取质询工件的说明写上 SQL 检查结果的片段照 SQL 换
上一处改动(f0e97f6)只在接口说明和 _masked_checks 里写了,模块说明的「读取」一条和 inquiry_artifact 的说明补上,免得读的人以为工件里的 checks 原样给。
2026-10-06 12:34:55 +08:00
ThearchyHelios 8ca750d7ee 审批质询遮罩收口:说明跟上实现——「夹住」越过的连接词含异或、条件长到细看不过来时多遮几个阈值写进已知局限
- 模块说明的已知局限补一句:条件长到细看不过来(PIN_WORK)时,两边都隔开的一律按夹住算,会多遮几个阈值
- 测试说明对齐:同一个条件里夹住的那组用例越过的连接词含异或(XOR 用例),HAVING 的用例已挪到自己那条测试;年龄段那条
  补上说明
2026-10-06 12:32:48 +08:00
ThearchyHelios f0e97f614f 审批质询遮罩收口(后端):质询工件里的 SQL 检查结果字段照证据面板,片段里的遮罩值照 SQL 换掉
做项 3 时发现的同一类缺口:agent 的查询快照带着 SQL 检查结果(数据源查询工具对照数据目录查出的问题,checks),质询工件接口
原样带出——sql_excerpt 是从这条 SQL 摘的片段(按遮罩列筛选的那个条件,例如 buyer_phone = '13900000001'),for_model 是给模型
的改法、也照抄了片段。事件和工件里的 content.sql 已经换掉遮罩值,旁边的片段没换,等于从旁边把值露出来;证据面板对报告快照
本来就只给约定的几个字段、不给 for_model。

- inquiry_artifact 里的 checks 经 _masked_checks:字段照证据面板的 _query_checks(不交 for_model),sql_excerpt 照查询清单的
  SQL 换(_scrub_sql,和遮罩列做等值比较的短的数、日期这样的字也认得出)
- 界面不读 checks,只是接口上不再带原值
- 补单元测试和端到端测试(把 SQL 检查换成会摘出遮罩值片段的假检查)。做过去掉实现、测试变红的验证
2026-10-06 12:30:52 +08:00
ThearchyHelios 32c3119086 审批质询遮罩收口(后端):质询工件里文字格夹着的遮罩值按子串换掉,回答一路照旧整份不交
第 1 轮起留着的缺口:备注列(没设遮罩)一格写着「客户13900000001来电改址」,手机号设了遮罩、被报告取数选中。agent 引用这一格,
回答被正确拦下(「回答中含有设了遮罩的值……已不予展示」),可事件的 inquiry_artifacts 照样记着这份快照,
GET /api/runs/{id}/inquiries/artifacts/{artifact_id} 返回原样的格子、masked 为空,等于在「不予展示」旁边一点就能看到原值。

- _masked_view 加 embedded:没设遮罩的列里,一格文字(JSON 列按界面画出来的样子)夹着按值认得出的遮罩值,把那一截换成
  「已遮罩」,这一格别的字、别的格子照原样;门槛同整格(_Secrets.scrub:够长的数、两个字以上的文字,提问人自己写的、报告
  列出的指标和运行输入的值不换)。这一列不记进 masked:界面把记进去的列整列画成「已遮罩」,这一列在数据源里也没设遮罩
- 只有取质询工件这一路按子串换,而且只换要取的那一份(_plan 的 embedded 是工件 id 的集合);回答一路不动,渲染出来露了
  照旧整份不交
- 接口说明写清 masked 的含义:有格子整格被遮掉的列;一格文字里夹着的只换那一截,列不记进去
- 补单元测试(文字格、字典形状的行、JSON 列、整格照旧整格换、回答一路照旧)和端到端测试(回答整份不交,工件里那一格变成
  「客户已遮罩来电改址」、其余格子照原样、masked 为空)。做过去掉实现、测试变红的验证
2026-10-06 12:25:48 +08:00