Commit Graph

932 Commits

Author SHA1 Message Date
ThearchyHelios 656a550408 数据事件触发按复核意见修正:途经驳回、去抖积压、source.changed 补写、按期次分窗口、补算缺省不认、示例出口字段同一套过滤
M1 途经驳回(编排者已定):outbox.stage_run_finished 收可选的 rejected_via(缺省 None 即没途经驳回),事件内容带 rejected 和
rejected_via,说法接「(途经驳回)」,模板有 {{ event.rejected }}(true / false)。触发条件缺省只认没途经驳回的,trigger 加
include_rejected(界面「也认途经驳回的」勾选,说明照实写)。runner._finalize 把 _rejected_via(state) 传进来那一下留给编排者
在合并时接(runner 的 _rejected_via 在 W1-b 的分支上);测试直接调 stage_run_finished 传参覆盖。

C1 去抖积压:以前一拍只走一个窗口,积压时后面的越等越久、全过了宽限,而且每个窗口各记一条跳过,原因还说调度没在跑。
现在 _drain 按发生时刻把等着的事件切成窗口,一拍里把到点的处理完(最多 100 个窗口、5000 条);排在前面已过补发宽限的合成
一条 skipped_misfire(合并了几次事件、几个窗口、时间跨度,上传表格另写合掉的期次),派给任务时已经晚于宽限才说「调度未运行」,
否则照实说积压;宽限内的照常发起(照重叠规则跳过的照实记)。

C3 source.changed:这一处确实没法和变化记录同一个事务(一次探查、剖析的变化分好几批落库,一批只触发一次要等任务结束才知道),
改成说实话:models_triggers 的说明写清哪两种同事务、哪一种不是。trigger_events 加 dedupe_key(source.changed:<任务号>,唯一),
emit_source_changed 按它去重;调度的一拍(刚起的服务第一拍就补,之后每 10 分钟)由 backfill_source_changes 按任务号补写:2 天内
测到、还开着的要处理级变化,任务已结束却没有事件的补一条。分组在 Python 里做,不用各库写法不同的 JSON 取值。

C4 按期次分窗口:triggers.window_key 让上传表格按期次各自成窗口,9 月、10 月连着启用各有各的触发;同一期的重传照旧合并。
两个窗口结束在同一刻时后一个挪 1 微秒,fire_at 不撞唯一约束。

C5 应用补算:workflow.finished 缺省不认 app_backfill 发起的(升版补算连发好几条、各是某一期的历史输入),trigger 加
include_backfill(界面「也认应用补算的」),选了「只认应用补算」时本来就认;说法写「途经驳回、应用补算的不算」。

C6 示例出口字段:triggers.outputs_of 一处过滤「_」开头的键,真派发、预览、立即运行共用;示例事件也按 matches 跳过缺省不认的
途经驳回、应用补算,取合条件的最近一次。

测试:新加 test_schedule_events_review.py(11 项,先写、先看着失败;去掉 matches 的驳回判断、把每拍窗口数改回 1 回跑,对应用例
各自失败),test_schedule_events_api.py 加一项「也认」两项经接口存取和说法;check-schedules 加「上游工作流的请求体」一段、纯函数
几条、触发历史里合并跳过那一行的夹具。
验证:后端全量 14479 通过;tsc、check-copy;check-schedules 447 项、check-runs 327 项通过(拿锁、--stacks 1)。
2026-10-18 18:09:20 +08:00
ThearchyHelios 35611beb7d 数据事件触发:去抖中的等待状态补一条断言(几次在等、窗口何时结束)
pending_for 给接口的 trigger_state:派给任务、还在去抖窗口里的条数和窗口结束的时刻。原来只在没有等待时断言过 due_at 为空,
这里在两条事件落进同一个窗口、还没到时刻时核对 waiting=2、due_at 是第一条发生之后 60 秒。

验证:后端全量 14467 通过(-n 8)。
2026-10-18 16:15:28 +08:00
ThearchyHelios 3f1eeff841 数据事件触发(界面):定时页新建时选「定时 / 事件触发」、事件那一节、列表与详情的等待状态、触发历史和运行详情的「由事件触发」
做了什么:
- 新建定时任务(目标是工作流时)先选怎么触发:定时 / 事件触发(TriggerEditor 的 TriggerModePicker);修改时只读地写出触发方式、
  说明建好之后不能改。选了事件触发,频率那一节换成事件(EventSection):三种事件、对象下拉(能选哪些、不能选的原因由新接口
  GET /api/schedules/trigger-options 给,界面照着画、选不了的置灰并写原因)、上传表格的期次(只认新增的一期)、上游工作流的
  只认应用 / 只认来源和触发链上限的说明、去抖、时区;换事件种类时对象和过滤清掉。
- 预览按一次事件渲染输入(对象最近一次的事件,没有时照实说是示例值),不列触发时间;变量按事件种类取(api.schedules.eventVariables),
  插得进 {{ event.* }};请求体带 trigger_kind、trigger,cron 交空串;修改时只交改了的触发条件。model.ts 加 TriggerForm 与
  triggerBody / switchEvent,表单问题里事件触发不查频率、要求选对象;服务端 422 指 trigger 时落到事件那一节。
- 列表、详情:「事件触发」标签,频率处写触发说明;下次运行写在等什么(去抖中收到几次、何时合成一次触发;有事件等待派发;调度
  没开时照实说不会派发)。触发历史:类型「事件」,写「由事件触发:…」、合并了几次、第几层;立即运行按最近一次事件发起的写
  「按事件:…」并标示例值。运行详情页头写「由事件触发:…(定时任务「X」)」(RunDetailView 只多传一个 trigger 属性),立即运行
  按事件发起的写「立即运行定时任务「X」,按事件:…」,不说成由事件触发;应用页印章同样不算。
- 文案进 lib/termsTrigger.ts;SCHEDULE_TEXT 只补触发类型「事件」和「还需处理」里的「触发事件」。立即运行按示例事件的说法
  不再在服务端加「(示例值)」后缀,是不是示例值看 real。

为什么:总方案 §4 要求定时页能建事件触发的任务、列表 / 详情 / 触发历史 / 记录页照实显示;能不能选、为什么由服务端判,界面不放逻辑。

验证:tsc、check-copy;check-schedules 加纯函数一段(表单 ⇄ 请求体)和三段页面(新建的请求体、列表 / 详情 / 触发历史、运行详情
页头,含 390px 浅色),假接口加了触发选项、事件预览、事件变量、事件触发任务的夹具,写请求断言请求体;原有各段照过;check-runs 照过。
2026-10-18 16:11:55 +08:00
ThearchyHelios a1ab3247f9 数据事件触发(后端):待派发事件表、调度循环派发与去抖、防环、{{ event.* }} 模板变量、接口收 trigger_kind / trigger
做了什么:
- 新表 trigger_events(待派发事件)、trigger_deliveries(一个事件派给一个定时任务、等去抖),放在 db/models_triggers.py,
  models.py 末尾整模块导入。事件在发生处、和那件事同一个事务里写一行:上传表格按期累积的新一期启用(table_versions 的
  publish_build / publish_snapshot 切指针那一下,移除、作废、回滚不算)、正式运行成功收尾(runner._finalize 写终态的事务里
  加两行调用 outbox.stage_run_finished)、探查 / 剖析新记了要处理的变化(source_changes.notify_after_job,一个任务一条)。
  发生处不发起运行:事务边界、重复与重启都在派发这边说清,也为第 2 波多进程做准备(认领靠条件 UPDATE)。
- scheduler/outbox.py:调度循环每一拍(fire_due 之后)派发:pending 事件条件 UPDATE 认领成 routed,同一事务里给合条件、启用着的
  事件触发任务写 delivery;窗口从最早一条的发生时刻起算 debounce_s(缺省 60 秒),过了就合成一次触发,内容取最新的那条,
  fire_at 取窗口结束的时刻(同一窗口永远同一个时刻,触发记录唯一约束兜住重复派发);照 fire_once 发起正式运行,ScheduleFire
  kind=event、inputs 写 {event: 事件说明, inputs: 渲染出的输入};上一次没结束记 skipped_overlap;派发太晚(超出补发宽限)
  记 skipped_misfire;任务停用、改了触发条件时等着的标 dropped。AGENTLAB_SCHEDULER 关着时没有这一拍,事件留着,接口照实说。
- scheduler/triggers.py:三种事件的触发条件(table.version 可选只认新增的一期;workflow.finished 可选只认某个应用 / 某种来源;
  source.changed)、核对对象(表格是按期累积的上传表格、上游工作流已发布、应用在)、说法、{{ event.* }} 变量(table、version、
  period、activated_at;workflow_id、run_id、outputs.<字段>;source、counts)、示例事件(预览、立即运行用对象最近一次真实的事件)、
  防环:保存时查 workflow.finished 串成的环(停用的也算,A→B→A、自己触发自己都拒,422 写出经过的工作流),运行时按 origin 里的
  触发深度兜底(超过 5 层记未能发起)。
- service:fire_once / _fire_workflow 接事件(模板按事件种类校验、渲染 {{ event.* }},运行来源 origin.trigger 带种类、对象、
  事件 id、深度、经过的运行和一句说法);check_template / render_inputs / variables 加可选的 event 参数,cron 任务一字不变。
- api/schedules.py:收 trigger_kind(cron 缺省)/ trigger,event 时目标只能是工作流、cron 存空串、下一次触发为空;返回带
  trigger_kind、trigger、trigger_text(也是 description_text)、trigger_state(几条事件在等、去抖到何时、调度开没开);触发
  历史带 event;预览按示例事件渲染;变量清单按事件种类给;新增 GET /api/schedules/trigger-options(各事件能选的对象及不能选的
  原因、来源、应用,界面不自己判);立即运行按示例事件发起;触发方式建好之后不能改。Webhook 照旧可配,通知写「事件触发」那一行。
- 应用页印章的来源(apps/resolve._from_text)照实写「由事件触发:…」。

为什么:总方案 §4 已拍板接这三种事件、不接口径发布,一套定时机制(X4);派发放在调度循环里而不是发生处,事务和重复都说得清。

测试:test_schedule_events.py(三种事件各触发一次、去抖合并与窗口外另起、重叠跳过、停用 / 改条件丢弃、调度关着时事件留着、
超出宽限、触发链深度与上限、模板校验与渲染、期次写法、通知、印章)、test_schedule_events_api.py(建、核对、防环、改、预览、
变量、立即运行、触发状态、可选对象);test_schedules_api.py 的约定键集合补上新加的几项。
2026-10-18 15:53:08 +08:00
ThearchyHelios 3f3e39e4d5 编排完善第 1 波骨架:定时任务加触发方式(trigger_kind:cron | event,trigger 写事件种类和对象)两列与迁移,存量行都是 cron(docs/design/orchestration-round/00-总方案.md §4) 2026-10-18 15:25:13 +08:00
ThearchyHelios 153e48a568 合入真实模型验收后的修正:口径标签逐个数判(报告引用了一条查询的几列时,每个数按它那一列投影后和口径的指标列比,判定仍只走 match_template / explain_sql_diff,投影只在去掉别的列不改变这个数时做;整题等价照拍板 L1 不放宽),差异说明在投影之后说得清(多查了几列、COALESCE、ROUND、按当天现算的日期、取不到参数、LEFT JOIN ON 里的日期条件);转抄口径卡指标的出处跨卡追到取数查询(对账、召回不再降档,发布前的静态检查同一套说法);认证队列按规范形分组;问题聚类的类 id 由成员定;use_caliber 的协议写清 card;评测「期望没有数据」只认数值格和叙述里的数、逐题比 SQL 比产出答案的那一格并按引用的列投影、判错时给配对上的那一格、估不出金额时不写 0(评分规则第 2 版);被取消、超时的模型调用也计一次;助手只给思考不给操作时问数据和评测都自动重来一次;零售样例题集的期望 SQL 去掉取整;目录两种计数照实说;自由形应用入口与岗位显示名。验收:后端全量 14435 通过;tsc、文案检查;相关浏览器检查(decode、stream、chat、evidence、ledger、evals、publish、inbox、verify、compare 等)全过;泄露扫描干净;在隔离服务上复验:自然问法里和口径写法一致的那一格标等价已证明,COALESCE、现算日期的照实标相关并说清差在哪 2026-10-17 13:36:08 +08:00
ThearchyHelios 3d9dc7239b README:口径账本逐个数判与取数 SQL 别取整的建议;第 4 波升级说明补上最终验收后的修正(比对算法第 4 版、评分规则第 2 版可重评、只思考不出操作自动重来、未完成的调用也计次) 2026-10-17 13:35:39 +08:00
ThearchyHelios 9627953445 差异说明认得一边套了 ROUND:说「X 套了 ROUND(…, 2):保留 2 位小数,另一边没有取整」,只陈述写法、不断言值不同,判等价照旧不等价;零售样例题集的期望 SQL 去掉 ROUND(…, 2)(取整交给展示,评分本来按容差比数),评测逐题比 SQL 不再因为期望里的取整一律对不上 2026-10-17 13:31:25 +08:00
ThearchyHelios eb42c12f05 合入 nr6/c:口径标签逐格判(引用了好几列的查询,每个数按它自己那一格投影后和口径的指标列比,判定仍只走 match_template;整题等价照拍板 L1 不放宽);差异说明在投影之后也说得清(COALESCE 套聚合、按当天现算的日期、取不到参数);评测逐题比 SQL 取和期望配上的那一格 2026-10-17 13:18:02 +08:00
ThearchyHelios 39b89fd36b 差异说明在投影之后也说得清:COALESCE 套聚合、按当天现算的日期、取不到参数
逐格投影之后两边只剩一列,差别常常就在这一列的写法上,以前照样整段兜底。只改说明,判定一个不变(这几种本来就不等价):

- _Explainer._coalesced:同一位置上一边是 COALESCE(聚合, 常量)(IFNULL 也读成它)、另一边就是那个聚合,说「这次的销售额
  套了 COALESCE(…, 0):没有符合条件的行、或者 amount 都为空时这次是 0、口径是空」;参数那一列表结构里一定不为空时只说
  「没有符合条件的行时」;分组时说「某一组的 amount 都为空时」,参数一定不为空时两边一样、不说。只认会得出空值的
  SUM / AVG / MIN / MAX,COUNT 套 COALESCE 两边其实一样,不说成不同。列名从原文取(规范形式去掉了外层别名)。
- _Explainer._relative:同一列上一边按运行当天现算(CURRENT_DATE、now()、SQLite 的 date('now', …),_now_based)、另一边写定
  的常量,说「这次对 paid_date 的条件是 …,按运行当天现算(换一天运行,范围随之改变);口径写定的是 …」,要求同 _rewrites。
- match_template:口径有入口字段取不到值、又没有缺省值时,以前拿空值渲染出「paid_date >= ''」去比,说明里就出现不存在的
  写法;改成一律说取不到参数,这次在那里写的是现算日期的逐个说清(_now_slots 按列和比较的方向认,只是说明)。解析不了的
  照旧只说解析不了。评测逐题比 SQL 用的是同一个函数,跟着说得清。

测试:tests/test_ledger_cells.py 新增三条(COALESCE 那一格仍然不等价、说明先说多查了几列再说 COALESCE;在虚构库上没有行时
两边确实不同,COUNT 套 COALESCE 不说成不同;按当天现算的日期逐格比时说清取不到参数、和写定日期的 SQL 直接比时说清一边现算
一边写定)。去掉这三处再跑,三条失败;判等价的语料和执行守门照旧全过。
2026-10-17 13:09:55 +08:00
ThearchyHelios 271bc6e782 口径标签逐格判:引用了好几列的查询,每个数按它自己那一格标
真实模型下问「上周的销售额是多少」,助手写 SUM(amount) AS 销售额, COUNT(*) AS 订单数, MIN / MAX(paid_date) AS 区间首日 /
末日,报告四列都引用了,口径「周销售」只取 SUM(amount)。按「全部引用列」投影后还是 4 列,对不上口径的 1 列,整条比、
四个数都标相关口径,说明整段兜底。

- ledger/match._cells / _cell(algo 4):整条(含按全部引用列投影)没证明、又引用了这条查询两列以上的,被引用的每一列单独
  投影到那一列,和每份口径的每个指标读的那一列(card_reads;口径版本没有口径卡的按取数的每一列)逐个比,仍只走
  match_template / explain_sql_diff,上次的投影规则照用:投影不了(分组列、DISTINCT……)、过了时限、不可比的格照用整条
  查询的那一个候选。结果记在 queries[].cells {列名: {status, candidates}},另记 one_row(sql_semantics.one_row:不分组的
  聚合,一定只出一行)。整条这一层的结论、候选一字不改;老工件没有 cells,照旧按整条读。
- ledger/labels:单元格按它那一格的结论标(_view);指标用了同一条查询的好几格时,只有那条查询一定只出一行才把逐格的
  结论合起来用(多行的结果里每一列各自对得上,不等于每一行的几列一起对得上)。整题照拍板 L1 不变:每个数都证明来自
  同一份口径才等价;一格证明了、别的格只是相关的整题不等价,原因写「有的数和相关口径不一致,不来自口径」(以前一律说
  「这次的推理」)。出具入口照 ledger/issue 现有判法只在整题等价时给,没有放宽。
- 逐格证明了的口径各处都算上(labels.candidates_of):工件 meta 的 calibers(召回按旧版本反查)、ledger/uses 的使用记录、
  召回时找原答案对应的那一格、E4;E1 另数各种结论各几格(cells),E2 的兜底比例按标签上给人看的那一份说明数。
- GET /runs/{id}/ledger 的 queries 带上逐格的 cells(遮罩同候选);证据片段的 ledger 键、轮次摘要的计数本来就按每个数的
  标签,数据变了、形状不变。前端只补类型;check-ledger 加一段:同一条查询一格来自口径(正文挂小标记)、一格相关(差在哪
  先说多查了几列),整题不等价不给出具。
- 评测逐题比 SQL(eval/score.sql_match)取和期望配上的那一格(score.matched_cells:评分时配上的格),没给时取逐格证明了
  的那一格;结果带 column。runner、rescore 传它。

测试:tests/test_ledger_cells.py(同一条查询一列等价、一列不等价各自标对;两份口径只证明了一份标那一份;口径多个指标按列
配对;指标跨两格只在一行的查询上用逐格结论;分组列、DISTINCT 逐格也不投影;照真实例子造的虚构库真跑一遍:销售额标等价
已证明,订单数、首末日标相关,整题不等价、不给出具、不自动出具,证据片段的 ledger 键一致;评测取配上的那一格)。去掉逐格
再跑,10 条失败;去掉「一行才合起来用」、去掉评测取格,各 1 条失败。
2026-10-17 13:07:19 +08:00
ThearchyHelios f520a88891 静态地认指标出处也跨卡往下追:转抄同一张图里另一张口径卡的指标,按那个指标的式子接着认(最多 MAX_COPY_DEPTH 层),受管级发布检查、应用检查和运行时、证据面板同一套说法;被抄的卡按 caliber_from 固定在别处版本的照旧停在那张卡 2026-10-17 12:31:13 +08:00
ThearchyHelios 124e1c0f49 评测逐题比 SQL 和账本同一个口径:传报告引用了哪几列(ledger/match.referenced_of),整条比不等价时按引用的列投影再比;「多查了几列」那一条不算差在哪个方面 2026-10-17 12:29:20 +08:00
ThearchyHelios f8d89e780c 合入 nr6/b:评测「期望没有数据」只认数值格和叙述里的数、逐题 E1 比产出答案的那条查询、判错时给配对上的那一格、估不出金额时不写 0、没做完的模型调用也计一次、助手只给思考不给操作时自动重来一次(问数据与评测同一个决定)、目录两种计数照实说、自由形应用入口与岗位显示名;评分规则升到第 2 版 2026-10-17 12:28:04 +08:00
ThearchyHelios 94f028fcef 合入 nr6/a:认口径按答案引用的那几列投影后再判等价(判定仍只走现有的 match_template)、差异说明投影之后再说并认得 LEFT JOIN ON 里的日期条件、转抄口径卡指标的出处跨卡追到取数查询(对账、召回不再降档)、认证队列按规范形分组、问题聚类的类 id 由成员定、use_caliber 的协议写清 card 2026-10-17 12:28:04 +08:00
ThearchyHelios d4f1ba8dd4 给评测留一个按引用的列投影的入口(referenced_of),顺手折行、补模块说明
- ledger/match.referenced_of(sealed):这次运行封存的报告引用了哪条查询的哪几列,正好是 compare_queries 的 referenced。
  评测逐题比 SQL(eval/score.sql_match,另一组在改)传它,就和账本一样按引用的列投影,不必自己再拼一遍。compute 和它
  共用读指标集的那一段(_card_loader)。
- data/sql_semantics 模块说明的「几处共用这里」补上模板绑定、按引用的列投影、同一个形状三处。
- 本轮几处新代码折到 120 列以内(只换行,不改逻辑)。

测试:tests/test_ledger_projection.py 的整题等价那条另核 referenced_of 交回的就是被引用的那一列。
2026-10-17 12:22:48 +08:00
ThearchyHelios 8c2ff49b50 use_caliber 的协议写清 card 填什么,写错了交回的原因能一次改对
真实验收里模型第一次常把 card 写成自己起的 c1 被拒,之后才改对:协议示例写的是 "card":"口径卡节点 id",紧接着又说
「口径卡(节点 id 就是 c1)」,两个 id 混在一起。

- api/copilot_context.USE_CALIBER_PROMPT:示例写 "card":"card";说清 workflow_id、version、card 原样照抄,只改 id、metrics、
  inputs;card 是那一版工作流图里口径卡的节点 id(查看结果的写法里写好了),id 是这次选用自己起的名字、生成的口径卡用它当
  节点 id;add_edge c1 → report 里的 c1 是自己起的 id。
- look/calibers.DETAIL_COMPOSE_PROMPT:同样写明 card 原样照抄、id 自己起。
- ledger/compose:card 写错时说清 card 要填节点 id、可填的有哪几个(「节点 id」(口径卡「名字」)),写成了自己起的 id 的
  另说一句「「c1」是你给这次选用起的 id,不是口径卡的节点 id」;几张卡要选时同样列节点 id。校验不放宽。

测试:tests/test_ledger_compose.py 新增两条(协议写清两个 id;card 写成自己的 id 被拒、原因说清该写什么,照着改成节点
id 就过)。改回原来的文字再跑,两条失败。
2026-10-17 12:17:21 +08:00
ThearchyHelios 1b31c49a00 问题聚类的类 id 由成员定,「不是口径」按成员记
类的 id 以前按种子问题的原文算:种子是一类里最新的那一句,时间相同的几句读出来先后不定、来了一句新问法种子就换了,
两次读取 id 就变,认证队列里点过的「不是口径」随之失效。

- app/chat/questions:Question.key 是稳定的 id(问数据的那一轮 turn:…、别的探索运行 run:…);QuestionCluster.id 是成员的
  id 排好之后取的哈希,同一批问题两次读取一样;recent_questions 同一时刻问的几句按 id 排出先后,不靠库里读出来的先后。
- ledger/certify:问题聚类那一项的 id 用类的 id;「不是口径」时服务端按同一批问题重新聚一次,记下这一类现在的成员(不收
  客户端传的成员,找不到、取向量出错就只记 id);之后这一类过半的成员是某一次记下的,就照样不进队——来了几句新问法 id
  会变,按成员认不失效。以前按种子原文算的 id 记的照样认;记的成员读不懂的照实忽略。
- api/ledger:dismiss 把当前身份交给 certify(重新聚类要按这个人读得到的问题)。

测试:tests/test_ledger_certify.py 新增三条——同一批问题换个先后读,种子不同、类的 id 一样;点过「不是口径」之后换个说法
再问一句,成员变了照样不进队,读不懂的老记录不报错也不藏;按旧的种子原文 id 记的照样生效。把 id 改回按种子原文、把按
成员认去掉再跑,各自的用例失败。
2026-10-17 12:15:07 +08:00
ThearchyHelios 67b13a921b 评测页检查守住估不出金额时的写法;估算说明去掉重复的半句
接着「评测估算和每题平均花费:有估不出金额的调用时不再写成 0 美元」那一项:check-evals 加一节,在浏览器里核对开跑对话框
上一次都估不出时写「估不出花费」和原因、不出现 $0,估出一部分时写「至少约 …(每项至少约 …)」;评测总览都估不出时花费写
「金额无法估算」、不写每题平均,估出一部分时写「≥」和「每题至少约」。

在浏览器里看到「估不出花费 · …估不出花费:…」说了两遍,runner.estimate 全估不出那一句的说明去掉了重复的「估不出花费」。

验证:check-evals 110 项全过(新加 5 项);test_eval_runner.py::test_the_estimate_says_it_cannot_price_instead_of_zero 通过。
2026-10-17 12:11:55 +08:00
ThearchyHelios 72d9d2770c 认证队列的确定性重复按规范形分组:条件换序、AND 两边换位的进同一组
以前按 normalize_sql 的规范文本分组,WHERE 的条件照原文的先后,同一个形状换了条件顺序就拆成两组,凑不够门槛进不了队。

- sql_semantics.sql_shape:在判等价同一套不认列的规范形式(_light)上把字面量抹成只看类型(字还是数)的占位,再把交换律
  成立的地方按抹掉之后的写法重排——AND / OR 的各项、IN 列表、= / <> / + / * 的两边、比大小的两边(换边时大于小于跟着
  换);抹掉之后一样的几项按原值排,同一组里字面量的位置对得上。不另写解析器。认不出的(解析不了、sqlglot 会读错的写法、
  字面量对不回 normalize_sql 的)为 None,照旧按规范文本分组(只会分得细)。positions 把规范形里的字面量对回 normalize_sql
  的位置,固化成模板时用。
- ledger/certify:Shape 加 key / canonical / positions;队列按(数据源、规范形)分组,跨轮变了的位置按规范形对齐,再换回
  代表那条的位置;固化时同组的别的运行也按规范形认、按同样的对齐认参数。这一项的 id 按规范形算;以前按规范文本记的
  「不是口径」照样认(组里任何一条的规范文本对得上就算)。

测试:tests/test_ledger_certify.py 新增四条——三种换序写法同一个形状、字面量位置对得上;七种真不同(换列、OR、少条件、
换运算符、数对字、IN 列表、换聚合)不是同一个形状;真跑三轮换序的问数据进同一组、多筛一个地区的不进,固化出两个日期
参数;按旧 id 记的「不是口径」照样生效。把分组改回按文本再跑,换序那条失败。
2026-10-17 12:09:58 +08:00
ThearchyHelios c3123e3818 转抄口径卡指标的出处跨卡往下追,对账、召回运行按追到底的结果判档
对账卡的「口径」、召回卡的「旧版 / 新版」写成 nodes.<口径卡>.metrics[i].value,出处停在「来自口径卡指标」,对账运行、
召回运行一律降档出具(「输入没追到原始数据」);口径卡自己的指标明明追得到取数查询。

- engine/lineage:metric_copy 认出转抄一个指标的值的写法;traced() 对 via=metric 的输入看 upstream.traced;
  MAX_COPY_DEPTH = 8。
- engine/nodes/metrics:转抄的输入记 upstream {metric, artifact, depth, traced}——那张卡的指标集落了工件、那个指标的输入
  都追到了原始数据(它自己转抄的已经一层层追过,记在它的输入上)、层数没超上限才算追到;超了就算没追到(防环:循环里
  抄自己上一轮的卡每一轮多一层)。那个指标不完整、所依据的查询没通过 SQL 检查的,转抄出来的同样不完整、存疑,出具照样
  降档:信任按追到底的结果给,不多给。
- api/evidence:转抄的输入后面接一步 upstream(那张卡的那个指标:值、原式、代入式、它的输入、追没追到,取值按同一套遮罩),
  转抄的转抄接着追(同一个指标只接一次、最多 8 层),它的输入落在查询快照上的照常给查询步骤;出处链里遮着的列把这几步
  的输入也算上。reconcile.py、recall.py 生成的式子不用改。
- 前端证据面板:新的一节「转抄的口径卡指标」,输入来源里转抄的那一项写清追没追到、点一下跳到那一步,那一步的输入点一下
  跳到口径的取数查询(文案进 lib/terms.ts)。check-ledger 加一段:对账结果里点「按口径」的数,一路点到口径的取数查询。

测试:tests/test_metric_copy_lineage.py(转抄、转抄的转抄完整出具;转抄没追到的照样停在那张卡;上游不完整的转抄也不完整;
层数上限;真跑对账、召回都完整出具,出处链接上口径卡那一步、给出口径的取数查询)。去掉 upstream 再跑,5 条失败。
2026-10-17 12:05:12 +08:00
ThearchyHelios 0b6cde5c0c 自由形应用的两处小问题:入口按钮「发起」后英文名空一格,岗位显示名不再拿岗位名充数
一、入口按钮写成了「发起accept 报销写回应用」:派生(apps/derive)、审批模板、自由形模板都是 f"发起{名字}" 直接拼。
加 apps/derive.start_label,名字以英文字母、数字开头时中间空一格(「发起 accept 报销写回应用」),三处都用它。

二、岗位标签显示原始名 finance:派生和自由形导入的图里只写了岗位名(画布上随手写的 id),应用里岗位的显示名就填成了它,
看着像起好的名字。加 apps/tree.slot_label:按用到这个岗位的节点在图上的名字拼(派给它的写节点名,几处用到用顿号连;
超时升级给它的写「…超时升级」,同蓝图模板 slot_labels 的叫法;动作确认派给它的写「…发送前确认」);节点没起名字、
没有节点用到它的照实用岗位名。派生和蓝图模板(app_base)共用:卡片上起了名字的(审批模板的步骤名)照旧用卡片上的,
卡片上就是岗位名本身的才按节点名拼。应用编辑器的岗位一节里,显示名和岗位名一样时写一句「还没有起显示名,现在用的是图里的
岗位名」(文案在 lib/terms 的 APP_EDITOR_TEXT.slotLabelRaw),不让人误以为那是起好的名字。

测试:test_blueprint_templates.py 的 test_entry_labels_keep_a_space_before_a_latin_name、
test_slot_labels_use_the_node_names_and_say_so_when_there_is_none、test_derived_apps_use_the_same_slot_labels_and_entry_label;
test_app_spec_tools.py::test_derive_a_process_leaves_slots_empty 原来钉的是旧行为(显示名 = finance),改成节点名「财务审批」。
去掉修复回跑四条都失败。
2026-10-17 12:00:58 +08:00
ThearchyHelios 78e9b0c6da 目录的「使用次数」和「用过这张表的运行」口径照实写:一个按查询计、一个按运行计
验收时同一张表「使用 4 次」、「共 3 次运行」对不上,接口说明(GET /catalog/{table}/usage、catalog_review.table_runs)
却写「取法同使用次数」,看着该是同一个数。两者数的确实是同一批查询快照(catalog.query_snapshot_tables),但使用次数按
查询计(同一次运行里查了两次算两次),用过这张表的运行按运行去重,前者可能比后者大——数没错,说明错了。

改了接口说明、table_runs 的说明和 catalog_review 模块说明里那一条,写清两种计法和为什么会不一样;表清单上「使用 N 次」的
悬停说明写明「按查询算」,并点出下面「用过这张表的运行」按运行算(运行列表那边的悬停说明本来就写清了,没动)。

测试:test_catalog_review_queue.py::test_usage_counts_queries_and_the_runs_list_counts_runs(同一次运行查两次:清单 4、
运行 3;接口说明不再写「取法同使用次数」、写明按运行和按查询)。去掉修复回跑失败。
2026-10-17 11:55:53 +08:00
ThearchyHelios 0488965352 探查范围外、没有注释的表中文需求命不中:照实写进模块说明,加一条测试钉住
真实模型验收里,范围外又没有注释的表只按英文名检索,中文需求命不中(data/schema_view 的 _outside_text 只放名字、拆出的词、
注释)。先查了有没有现成的英中业务词对照可以给拆出的词补上中文说法:命名推断只拆词、不翻译,catalog.name_words 也一样,仓库
里没有这样一份对照。按约定不临时编一份大词表(编出来的覆盖不全,还会把不相干的表抬上来),改成在模块说明里照实写清这个限制、
影响多大(真库多半有注释,注释照常参与检索;范围内的表另有目录中文名、列名)和怎么绕过去(按英文名点名查看会按需加入,或者
请管理员加进探查范围、补注释或目录中文名)。

测试:test_look_large.py::test_out_of_scope_objects_without_a_comment_match_only_their_english_name 钉住现在的行为
(英文名命中、中文需求命不中没注释的表、有注释的按注释命中),以后接了词表要跟着改。这一项只改说明、不改行为,没有
「去掉修复回跑」。
2026-10-17 11:53:54 +08:00
ThearchyHelios 3dc5f5add4 助手只写了思考、一个操作都没给时自动重来一次:问数据和评测走同一个决定
真实模型验收里 deepseek 偶发只出思考、不出操作,生成过程交出 error「助手本轮未完成:模型没有给出任何修改,也没有说明
原因」,问数据这一轮照实报了,但不会自动重来——而这种偶发情况重来一次多半就好,「重来一次」的规矩本来就有
(chat/turn 的 MAX_ATTEMPTS = 2,含第一次)。

- chat/turn:error 也由 next_step 判:机读码是 EMPTY_GENERATION(copilot_empty)的判 retry,别的仍判失败(被截断的不重来:
  额度不变,重来还是截断);error_reason 取重来时交给助手的原因(error 带的 reason,没有就去掉「助手本轮未完成:」前缀);
- api/copilot.answer_ops:一个操作都没给时 CopilotIncomplete 带上这个码;error 操作多带 reason 和 next(retry | fail),和
  final 的 next 一样由服务端定;
- 问数据页(store/chat.ts)照 error.next 走:retry 就带着原因重来一次(同 retry_instruction 的从零重建措辞,次数照
  MAX_ATTEMPTS),试满了才照实报;进度写「助手上一次没有给出任何操作,正在重新生成工作流…」(lib/terms 的 CHAT_RETRY_TEXT);
  第一次建图的用量照样记进这一轮;
- 评测(eval/answer.Driver.drive)用同一个 next_step / error_reason / retry_instruction:先重来一次,还不行才记建图失败、
  助手调用失败;每次自动重来的原因照实记在 turn.retries(没过检查、复核判不可用的重来也记),评测页逐题明细写出来。

测试:test_chat_turn_policy.py 的 test_next_step_branches(error 的两支和 error_reason)、
test_the_frontend_retries_an_error_the_server_says_to_retry、test_answer_ops_says_to_retry_an_empty_generation、
test_the_eval_driver_retries_an_empty_generation_once(重来后回答 / 重来还不行,两次的用量都算);
test_copilot_answer_ops.py 的 SSE 对照多认 error 上新加的 next、reason、code。去掉修复回跑都失败。
浏览器:check-chat 新加 emptyretry 一节(重来一次后照常回答、交回去的话带原因、两次用量都记;两次都不行照实报、不试第三次),
check-chat 840 项全过。
2026-10-17 11:51:34 +08:00
ThearchyHelios 12439b5305 差异说明投影之后再说;日期条件写在 LEFT JOIN 的 ON 里的说清楚
选择的列不同时,差异说明整段退回「两条 SQL 的写法不同,未能归纳成具体差异」(真实验收 E2 的兜底比例 45%):只差在选择
列表上时一条具体说法都没有;多选的列里有 sqlglot 不认识的函数(SQLite 的 printf 这类)时整段不算简单形状,WHERE 里
真实的差别也不写。

- ledger/match:按引用的列投影过、仍没证明的候选,差在哪用投影之后那次比较的结果;答案多选了列的,在最前面写一句
  「这次多查了 N 列,不影响这个数」(aspect=projection),然后照常说条件、时间、聚合上的不同。E1 的差异类型不数这一句。
- sql_semantics 的 _Explainer._left_on_times:两边来源一样、只差在一个 LEFT JOIN 的 ON 里的时间条件时,说「这次用 LEFT
  JOIN 关联 orders 表时只关联 … 的行,口径没有这个条件」「…时对 paid_at 的条件是 …,口径是 …」「…时按 paid_at 筛时间,
  口径按 created_at」。说得准才说:差的每个条件都是只涉及可空一侧那张表的时间条件、那张表只出现在结果里(WHERE、
  HAVING、别的连接没用到,SELECT 列表用到了)、同一列的按取值算过确实不同、单边的不是恒为真;套着日期函数认不准的、
  反连接、结果里没用到可空一侧的、不是时间条件的照实兜底。aspect 写 join(说的是关联条件),双盲复算不会把它当业务日期
  写进数据目录。只改说明,判等价不变。

测试:tests/test_ledger_projection.py(没证明时先说多查了几列、再说少了 status 条件,不再兜底)、
tests/test_sql_left_on_times.py(四种说得清的、在虚构库上结果确实不同;四种说不清的照实兜底;判等价的结论不变)。
去掉这两处改动再跑,各自的用例失败(2 条、8 条)。
2026-10-17 11:49:31 +08:00
ThearchyHelios 2a4086ab73 认口径按答案引用的那几列比:整条比不等价时先投影再交现有的绑定和判等价
真实助手的 SQL 总会多选几列(订单数、日期常量、MIN / MAX、COALESCE),报告只引用其中一列,口径的取数也只选它的指标要的
那几列。以前整条比,「上周的销售额是多少」3/3 只标相关口径、从不自动出具。

做法:sql_semantics 新增 project_select / project_template / output_names。按方言切词、按括号深度认出最外层 SELECT
列表每一项在原文里的起止,只把留下的几项原样拼回去(选择列表前后一字不动,保真守卫照样看得到原文),拼完重新解析、
语法树要和「原树换成留下的几项」一模一样。只在确定去掉不影响时投影,否则照实给原因、照旧整条比:DISTINCT / DISTINCT ON、
出现在 GROUP BY 里、被 ORDER BY / HAVING / QUALIFY / 窗口用到(按别名、同样的写法、按位置)、有 LIMIT / OFFSET 又不是
只出一行的聚合、集合运算、*、可能改变行数或不认识的函数、去掉仅有的聚合、留下按任意一行取值的列、调换先后时引用别的
选择项的名字,一律不去。

ledger/match:整条比不等价时,答案一侧按报告引用了这条查询的哪几列(labels.referenced_columns)投影,口径一侧按口径卡
上每个指标读了这个取数节点的哪几列(labels.card_reads)投影,投影后照样交 match_template(不另开给信任的路)。被引用的
列两边怎么对上写进模块说明:答案按列名在快照结果列里找位置、口径按 cell() 里的列名在输出名里找位置,投影后按位置配对,
答案留下两三列时另试调换先后,每份口径取数最多试 12 种。证明了的候选记 projection;没投影可试的逐对和以前一字不差。
算法版本升到 3;E1 另数 proven_projected;对账按投影后留下的列配对(不用人选)。

测试:tests/test_sql_projection.py(该去的、不该去的各一组,语料里 305 种投影在虚构库上执行守门)、
tests/test_ledger_projection.py(多选一列 / 多几个聚合 / 日期常量列能证明;多一个分组列、DISTINCT、按别的列排序加
LIMIT、HAVING 用到多出来的聚合仍然证明不了且和不投影时一字不差;口径一侧投影、调换先后;真跑一次整题等价;对账配对)。
去掉投影再跑,7 条失败。
2026-10-17 11:47:26 +08:00
ThearchyHelios 4aa3e6f01f 评测估算和每题平均花费:有估不出金额的调用时不再写成 0 美元
价格目录里没有助手模型时(真实模型验收的接入就是这样),第一次评测估不出、照实写了;第二次起估算按上一次的每题平均
花费算,而每题平均是「估得出的那部分」除以题数 = 0,开跑对话框写「约 $0」、评测总览写「每题约 $0」,像不花钱。

- eval/stats.summarize:每题平均花费一分都没估出来(有估不出的调用、估得出的部分是 0)时为 None,估出了一部分的照给;
- eval/runner.estimate:上一次的调用金额都估不出时照实写估不出(带次数和原因,金额上限只管得住估得出的调用);估出了一部分
  的标 at_least、带上另有几次估不出,说明写「实际至少这么多」;老总表里每题平均记成 0、却有估不出的调用的也按估不出算;
- 按角色用量里裁判那一份(runner.judge_usage)补上估不出的次数:以前只有金额和次数,裁判模型不在价格目录里时写成 $0;
- 评测页:开跑对话框估出一部分时写「至少约 …(每项至少约 …)」,总览的每题平均有估不出的写「每题至少约 …」,一分都没估出来
  的不写(金额那一格已是「金额无法估算」)。

providers/usage 的汇总形状不变:cost_usd 是估得出的那部分、unpriced_calls 是估不出的次数,和运行的 usage 是全系统一个口径
(细案 01 做法 3 定的),前端的金额写法(runCostText)一直按这两项一起写「≥」或「金额无法估算」;漏掉的是只读 cost_usd 去算
平均、做估算的这几处。模块说明里把这条写明了。

测试:test_eval_stats.py::test_the_per_case_cost_is_not_zero_when_calls_could_not_be_priced、
test_eval_runner.py::test_the_estimate_says_it_cannot_price_instead_of_zero(全估不出、估出一部分、目录价是 0 的才是真
不花钱)、test_eval_runner.py::test_the_judge_role_keeps_its_unpriced_calls。去掉修复回跑三条都失败。
2026-10-17 11:40:27 +08:00
ThearchyHelios 3c4dbe0c02 没做完的模型调用也计一次:挑表超时被取消的那次不再漏掉
真实模型验收里,挑表(look/pick 的 asyncio.wait_for)超时被取消的那次调用一点都不计:LangChain 的 ainvoke 被取消时
卡在 agenerate 的 gather 上,on_llm_error 根本走不到;astream 被取消时会回调,可一段都没收到的又被当成「请求刚发出去
就被拒了」不计。问数据一轮、评测按角色的调用次数因此少一次。

providers/usage.Meter 现在多记一项 unfinished(汇总里是 unfinished_calls):被取消、超时(按异常类型认,含各家 SDK 的
…Timeout)、流读到一半被关掉的调用都记一次调用、标未完成,token 只计已经报了的(一段都没收到的记 0、算没报全,
金额也就估不出);开始了、到汇总(和嵌套并入外层)时还没有结束回调的调用一并算未完成。请求一发出去就被拒的仍不计。
评测汇总相加(eval/answer._add_usage)带上这一项;评测页按角色的用量写「其中 N 次未完成」;问数据页的这一轮用量
相加时也留着它。

测试:test_usage_meter.py::test_a_call_cancelled_before_any_result_still_counts_as_an_unfinished_call(ainvoke 被
wait_for 取消、look/pick._ask 超时、astream 收到一段输入用量后被取消、被拒的不计);by_model 的形状多了一项,
test_metering_counts_by_model_and_prices_from_the_catalog 和评测同口径那条跟着改。去掉修复回跑失败。
2026-10-17 11:36:50 +08:00
ThearchyHelios 06c1e8bd25 逐题 E1 比产出答案的那条查询:答案没引用、又查了几条时不再硬比第一条
真实模型验收里,答案没有引用查询时逐题 SQL 比对取了第一条相关的查询,取到的往往是探索查询(看看有哪些地区那种),
「差在筛选条件」和答案的数毫无关系。现在 score.sql_match 先定哪条是产出答案的(_answering):答案引用了查询就比被
引用的那几条;没引用、这次运行(核查用的不算)只查了一条,答案的数只可能来自它,按它比、结果里写明依据
(basis=only_query);没引用、查了几条的不比,照实写「答案没有引用查询,这次运行执行了 N 条查询,没法对上是哪条
产出了答案」,记「没比对」、不计入 E1。答案引用的只有合并查询时也照实写没法比。

评测页逐题明细在依据是「只查了这一条」时写一句说明(文案在 termsEval 的 sqlMatchBasis,类型加 basis)。
算第 2 版评分规则的一部分:老评测按新规则重新评分时逐题 E1 跟着重比。

测试:test_eval_score.py::test_the_per_case_sql_match_compares_the_query_behind_the_answer(先探地区再查销售额、
答案没引用:不比;只查一条、答案没引用:按它比并写明依据;去掉修复回跑失败——修复前比的正是探地区的那条)。
2026-10-17 11:33:35 +08:00
ThearchyHelios f5204277aa 评测判错时「答案里的数」给配对上的那一格,配不上照实写配不上
真实模型验收里,退款金额那道题判错后「答案里的数」显示成了答案里的订单数 36,还出现过显示一格问题原文的:
_score_scalar 判错时取答案引用的第一个值,不看是哪一列,也不看是不是数。现在只在数值格里找和这一项配对上的那一格——
列名(或口径卡的指标)是期望取的那一列、那个指标,或者和期望的说明同名;名字配不上时,期望写了维度、维度对得上的数
恰好只有一个也算配上。配不上就不给「答案里的数」,原因里写「答案引用的数按列名、维度都配不上这一项(引用了 单数 36)」;
答案里只有文字格、没有数的写「答案里没有对得上的数」。

算第 2 版评分规则的一部分(SCORER_VERSION 已在上一个提交升到 2)。

测试:test_eval_score.py::test_a_wrong_answer_shows_the_paired_cell(按列名、按说明、按维度配上;只有订单数、同一行
几个数都配不上、只有文字格;去掉修复回跑失败)。
2026-10-17 11:31:05 +08:00
ThearchyHelios c103055c2a 评测「期望没有数据」的题:引用文字格、空值格不再算给了数
真实模型验收里,答案写「华中区没有数据」、引用了地区那一格(或者一格 NULL),评分把它当成「给了数」判错。
_score_none 以前只要答案里有引用值就判错,不看引用的是什么。现在「给了数」只认引用的数值格和叙述里写的数:
文字格(地区名这种维度)、空值格不是数,答案说的仍是没有数据,按没有数据判(有查询返回 0 行的照旧算可追溯)。
判错时照实给出是哪一格或哪个数。

评分规则升到第 2 版(SCORER_VERSION = 2),老评测可以按新规则重新评分(只读封存,不调模型)。

测试:test_eval_score.py::test_none_counts_only_numbers_as_giving_data(只引用文字格、引用 NULL、引用数值格、
叙述里写了数四种;去掉修复回跑,前两种判错、测试失败)。
2026-10-17 11:29:06 +08:00
ThearchyHelios fdd889b73a 合入最终验收(免模型部分)查出的 12 项修正:取值观察的「量跌」「缺天」不再拿今天这个不完整的日子比、按日行数只数到观察当天、「今天」统一按服务端所在时区;同一列条件被改写时差异说明说清两边的写法(判等价不变);用量计量记对模型名;探查范围外的表照实说「在探查范围外」;对被驳回的运行 resume 说「被驳回」;固化口径草稿用传入的名字、报告指令不写死日期、指标名取中文名、frozen_into 去重;剖析对话框可以直接发起取值观察,定时维护挑不出表时触发记录写明原因;评测结果给实际问的问题、没跑的题单独计 not_run、stop_reason 照实写;按需加入范围的留痕记上运行发起人;召回消息按种类写答案条数;撤销分享返回页面标题;E4 只算有口径卡的版本。验收:后端全量 14312 通过;tsc、文案检查;相关的 13 项浏览器检查 4423 项全过;四种库的方言容器测试全过;泄露扫描干净 2026-10-17 09:31:58 +08:00
ThearchyHelios 77b4c9c68a E4 的版本用量只算有口径卡的工作流
验收:E4 的用量把没有口径卡的普通发布工作流也当成口径计数。_e4 按(工作流, 版本)数所有 run_class=formal 的运行,
不看那一版是不是口径。

改法:数完之后按 WorkflowVersion 的图过一遍,只留图上有源头口径卡的版本(recall.source_cards:metrics 节点、不是
转抄别处口径的 caliber_from,和召回认口径卡的同一个判断)。比对里等价已证明的运行数(proven_runs)本来就只来自口径,
不动。写进了模块说明。

测试:test_ledger_api 新增一条:一个有口径卡、一个没有的已发布工作流各跑两次正式运行,E4 用量里只有前者、formal_runs
为 2。去掉修复回跑挂。
2026-10-17 09:19:24 +08:00
ThearchyHelios d9eaf8b850 撤销分享链接交回的 page_title 是页面标题,和建、列的一样
验收:DELETE /api/apps/{app_id}/shares/{share_id} 的返回里 page_title 是页面 id("home"),建分享时返回的是页面标题。
revoke_share 调 share_out(row) 时没给标题表,share_out 就退回页面 id。

改法:撤销照列表的取法(应用当前的版本 _previous 的 spec)给 share_out 标题表;撤销过的再撤销也一样。

测试:test_shares 的撤销用例加断言:建出来的 page_title 是「首页」,撤销、再撤销交回的都是「首页」。去掉修复回跑挂。
2026-10-17 09:19:24 +08:00
ThearchyHelios 590bfc9a59 口径召回的通知按种类分开写答案的条数
验收:ledger.recall 消息里写「答案:2 个」,实际是 1 个问数回答加 1 个正式运行。notify_recall 拿 _uses_of 的长度直接
拼成「N 个」,不分种类。

改法:notify/service 加 _answers_text,按用法的种类数(ledger.uses.KINDS 的顺序、KIND_TEXT 的叫法,同版本用量和召回
批次的说法)写成「问数回答 1 个、正式运行 1 个」;全局渠道按这一批汇总、个人渠道只数他的,两处都用它。只写条数,照旧
不放旧值、新值、差值和原答案的值。

测试(test_ledger_recall):新增一条:同一周一个问数回答、一次正式运行,升版召回之后全局渠道收到的「答案」一行是
「问数回答 1 个、正式运行 1 个」,消息里没有数;原来两条按「1 个」「2 个」断言的改成带种类的写法。去掉修复回跑挂
3 条。
2026-10-17 09:15:07 +08:00
ThearchyHelios 8f79098b6f 运行时 db_schema 按需加入探查范围的留痕记上运行的发起人
验收:db_schema 点名探查范围外的表、当场加进范围时,留痕(source_objects 的 added_by,trail_dict 的 by)是 null,
只有 run_id;look 那条路记着对话里的发起人。tools/datasource._on_demand 建 Trail 时没传 actor。

改法:查不在的原因时顺手按 ctx.run_id 取 Run.started_by,作为 Trail.actor 传下去。没有运行(ctx 没给 run_id)时照旧
为空。

测试:test_catalog_schema_truncated 新增一条:建一次 started_by 为「分析员甲」的运行,用它的 ToolContext 让 db_schema
点名一张范围外的表,留痕的 via 是 db_schema、run_id、ref(节点 id)对得上,by 是发起人。去掉修复回跑挂。
2026-10-17 09:12:29 +08:00
ThearchyHelios ccb2b2baa8 评测停止、中断、到了上限后的计数照实分开:没跑的题单独计 not_run,stop_reason 写停下的原因
验收:评测停止或中断后,没跑的题计入 errors、done = total、stop_reason 为 null,读起来像全评完了、出了很多错。
原因:runner._counts 把 cancelled、skipped 都算进 done(TASK_DONE),cancelled 又算进 errors;停止、中断走 _close →
_conclude(status, None, error),原因写进了 error、stop_reason 是 None;服务重启后的整理(startup、reconcile_stale)
和排队中就停掉(cancel_queued)只改了逐项状态,计数还是旧的、stop_reason 也没写。

改法:
- _counts:done 只算真的跑过的(ok、partial、failed);停止、中断时没评完的(cancelled)和到了上限、止损之后没开评
  的(skipped)单独计 not_run;errors 只算 failed;skipped 只算评了、但期望算不出、不评分的(verdict),不再混进没开评
  的。done + not_run + running + pending = total。
- 停止、中断是「停下」不是「出错」:_close 把原因记进 stop_reason(STOPPED / INTERRUPTED),error 留空;整体出错照旧
  记 error。到了上限、止损本来就写 stop_reason,不变。还没备好几臂就停下的那一支(bare)同样写 stop_reason 并重算计数。
- core/job_runtime 的 close_job、cancel_queued 加一个可选的 finish:状态改好、提交之前在同一个事务里补一步。评测的
  startup、reconcile_stale、stop 用它记 stop_reason、按改好的逐项状态重算计数(_closing)。起草、剖析、探查不传,
  行为不变(它们的测试全过)。
- stats.summarize 的 not_run 也只算没跑的(cancelled、skipped),出错的另列 errors(以前 not_run 里混着出错的)。

前端:评测页总览「N 项没评(停止、中断,或者到了上限、止损之后没开评)」和「M 项出错」分两行;类型补上 not_run、
errors 的说明。停下的原因本来就照 stop_reason 显示。

测试(test_eval_runner):停止之后 stop_reason 是「评测已停止」、error 为空,errors 为 0、done 是评完的题数、not_run
是其余,总表的 not_run、errors 同样分开;重启整理成 interrupted 时 stop_reason 是中断的说法、计数重算(not_run 1、
running 0);新增一条排队中就停掉的:not_run 等于题数、pending 归零;到了金额上限没开评的那题记 not_run、不记跳过。
去掉修复回跑挂 4 条。check-evals 的停止夹具照服务端改,核对停下的原因、不当成出错、没评的单列。评测、起草、剖析、
探查、定时维护相关的测试 548 条全过;tsc、check-copy 通过,check-evals 103 项全过。
2026-10-17 09:10:52 +08:00
ThearchyHelios a4486af90b check-evals:接住画布页起画布对话的 POST /conversations,免得偶发地漏到服务端
「口径命中与重复度」一节点「提取模板」会去画布(/studio/wf-drafted),画布页随后给这张图起一段画布对话
(POST /conversations {kind: canvas})。页面不关,这一下早晚不定:赶在最后那一节「写请求都在浏览器层拦下」之前
发出去就算漏到服务端。在 main 上连跑两次一次挂一次过,和这一轮的改动无关。照 check-ledger 的写法在假接口里接住
(交回一段空的画布对话),不进写请求的清单——不是评测页发的。改后连跑两次都过。
2026-10-17 09:02:57 +08:00
ThearchyHelios eb6abffc61 评测逐题结果给出这一题实际问的问题,模板原文另放 question_template
验收:评测结果列表里 question 是原始模板(带 {{ schedule.last_week_start }}),作答运行实际收到的问题已经渲染好了。
results 接口直接取的是 EvalCase.question。

改法:api/evals 的 _asked_questions 按执行时同一个算法、同一组输入渲染:题目模板 + 这次评测的截至时间(as_of,
建评测时就定下、重试不变)+ 题集时区 + 定时的 cron(runner._unit 交给 Driver 的就是这个)。题目在这次评测之后
改过(结果记的 case_hash 对不上现在的)时,现在的模板不一定是当时问的:改取作答运行输入里的 question,也没有
(反问、建图失败没有运行)就给 null,不拿现在的模板编一个。模板原文放 question_template。没有加库表的列:
渲染是确定的,不用另存。

前端:评测页逐题表的问法一格写实际问的问题;模板原文和它不同时(带变量,或题目改过)另起一行「题目原文:…」,
文案进 termsEval。类型补上 question_template。

测试:test_eval_runner 新增一条:带变量的题,结果的 question 和作答时 Driver 收到的一字不差、不含 {{,模板原文在
question_template;没有变量的题两者一样;题目改过、没有作答运行可取时 question 为 null、模板照给。去掉修复回跑挂。
check-evals 的夹具给带变量的那一题配上渲染好的 question 和模板原文,核对逐题表写的是实际问的、原文另起一行,
没有变量的题不多这一行。tsc、check-copy 通过,check-evals 102 项全过(漏到服务端的那个偶发问题另一次提交里修)。
2026-10-17 09:02:57 +08:00
ThearchyHelios f5c9f0c13c 剖析任务接口收取值观察范围,对话框给出「取值观察」;定时维护挑不出要观察的表时把原因记进触发记录
验收:取值观察只能经定时维护触发——POST /catalog/profile-jobs 的 scope 只收 needed、all,可剖析任务本来就支持
observe(只观察、不剖析)。另外 12 小时内观察过的表不再观察时,任务以 0 张表结束,原因只在剖析任务的 notice
里,从定时维护触发时触发记录(ScheduleFire.inputs)看不出来。

后端:
- ProfileJobIn.scope 加 observe(maintenance 是定时维护用的,接口照旧不收)。挑不出表时照旧 409 profile_nothing,
  说法是 NOTHING_TO_OBSERVE(「最近 12 小时内都观察过了」)。
- catalog_profile_jobs.nothing_reason:按此刻的表结构和目录一张表都挑不出来时交回 ProfileNothing 的原话;排在
  起草后面时说不准,返回 None。
- source_maintenance._start:建出 0 张表的剖析任务、这次触发又没有探查(开始时表结构不会变)、也没排在起草后面
  (目录不会变)时,判断得出任务会以 0 张表结束,把原因记进 inputs.skipped(同一套 NOT_RUN 说法进 reason)。
  任务照建、触发照旧是 started:每小时一次的维护不至于一天二十几次「没能发起」。同一次触发里有探查、或者排在
  起草后面时判断不了,保持现状,写进了模块说明。

前端:剖析对话框的范围里加「取值观察:有数据目录的 N 张表」(说法照维护设置里「取值观察」那一步:码值分布、
最新业务日期、按日行数和行数,和同口径的历史比出取值变化;最近 12 小时内观察过的这次不再观察),建任务带
scope: observe。张数按清单上有数据目录、不是视图的表算(isObservable,profile.ts),观察到期没有由服务端判,
所以是上限。挑不出表时说「没有要观察的表」和服务端原话,不给「改选全部表」(那是剖析,不是观察)。文案进
PROFILE_TEXT。

测试:test_catalog_observe 新增一条(接口建 observe 任务、只做观察检查,再建一次 409 profile_nothing,maintenance
422);test_source_maintenance 新增一条(只选取值观察,12 小时内再触发:任务 0 张表,inputs.skipped 和 reason 写明
原因)。去掉修复回跑两条都挂。check-catalog-profile 跟上:范围多一项 observe:1,说法核对;新一节建取值观察任务
(请求体 {scope: observe})、都观察过时的「没有要观察的表」且没有「改选全部表」。tsc、check-copy 通过,
check-catalog-profile 204 项全过。
2026-10-17 08:51:51 +08:00
ThearchyHelios c3173909db 整理:前两次提交里超过 120 字的两行折行,absent_message 进 source_objects 的 __all__
只动排版和导出清单,不改行为。
2026-10-17 08:42:26 +08:00
ThearchyHelios 9040289e1f 固化成口径草稿:口径卡用传入的名字、报告指令不写死代表那一轮的日期、指标名取目录中文名、已固化去重
验收报告里 certify.freeze 的四处:
- 口径卡的 caliber 用的是代表问题的原文,没用传入的 name。改成 name(去首尾空白)优先,没传才用代表问题。
- 报告的 instructions 写死成「回答:<代表问题>」,问题里带着那一轮的日期(「上周(10月5日到11日)」「九月第一周」),
  参数化之后这句就错了。有入口参数时改写运行时渲染的参数(「这次的参数:p1 = {{ input.p1 }},…;时间范围、筛选的
  取值以参数为准」),不写问题原文;没有参数时取数的字面量都是口径的常量,照旧写问题原文。
- 指标名直接用列名。现在能从数据目录取到中文名的用中文名:先看查询快照冻结的目录(schema_artifact,和口径比对用的
  同一份,ledger.match._frozen),老快照没冻结的看数据源现在的目录;只认列名本身就是目录里的一列(SELECT amount、
  SUM(amount) AS amount),gmv 这类算出来起的别名取不到,照旧用列名。
- 认证队列里 cluster 项的 frozen_into 把同一个工作流列了好几次:固化时同组的几次运行都记进 origin.sources,一项里
  的几次运行就各指一遍同一个工作流。shape 项同样会重。加 _frozen_of 按工作流去重(保持先后),三种项都用它。

测试(test_ledger_certify):原来的固化用例改成断言指令里没有代表问题和日期、有 {{ input.p1 }} {{ input.p2 }},
没传名字时口径卡照旧用代表问题,shape 和 cluster 两项的 frozen_into 都只有一个工作流;新增一条:传了名字的口径卡
用名字,目录里有中文名的列(amount → 金额)用中文名、取不到的照旧(n),没有参数时指令照旧写问题原文。去掉修复
回跑两条都挂。
2026-10-17 08:35:57 +08:00
ThearchyHelios 3f75cb1d2e 恢复被驳回的运行:照 end_reason 说「被驳回」,和继续运行同一个错误码 run_rejected
验收:对被驳回的运行调 POST /api/runs/{id}/resume,回的是「这次运行已取消,没有待处理的审批,无法恢复。已取消的
运行请重新发起」,/continue 却是对的(run_rejected)。resume 只看 status,cancelled 一律说「已取消」;存量的
failed + 驳回还会被说成「失败的运行可点继续运行」,而继续运行其实会被拒。

改法:runner 加 _resume_refused:cancelled + end_reason=rejected、以及加 end_reason 之前记成 failed 的驳回
(_rejected_before,同 continue_failed 的判断)抛 ContinueRefused(REJECTED_RESUME, code=RUN_REJECTED),别的照
_cannot_resume。resume 开头的状态检查、等审批凑齐时发现运行已经不在 interrupted 两处都用它。/resume 接口像
/continue 一样把带机器码的拒绝回成 CodedHTTPException(409, …, code)。ContinueRefused 是 ValueError 的子类,
别的调用方(待办服务)照旧按 ValueError 接。

测试:test_run_rejected 新增一条:驳回的运行 /resume 回 409 + run_rejected、说「被驳回」;手动停止的照旧说
已取消、不带这个码;存量 failed + 驳回也回 run_rejected,不再叫人点「继续运行」。去掉修复回跑挂。
2026-10-17 08:31:17 +08:00
ThearchyHelios c64c7a1062 目录接口对探查范围外的表照实说「在探查范围外」,不再说「可能已被删除或尚未探查结构」
验收:GET /api/datasources/{id}/catalog/{表} 对「对象索引里有、但在探查范围外」的表回 404「数据源中没有表 …,可能
已被删除或尚未探查结构」,而 /schema 和 /catalog/search 都说对了。_table_key 一律写死这一句,没看 absent_reasons。

改法:source_objects 加 ABSENT_MESSAGE 和 absent_message(),给人看的五种「不在」(表已不在、全库对象清单里也没有、
在探查范围外、在范围里却读不到结构、老缓存说不清)一句一句,和界面的 CATALOG_ABSENT_TEXT、/schema 用的
schema_view.absent_detail 同一套原因。上传表格、记录表按版本走,没有对象索引又没截断过的老缓存认不出名字是不是
从来就没有过:这两种只说「数据源中没有表 …」,不说「已删除」。用到的三处都换成它:_table_key(表详情、审阅、
使用记录等十来个接口共用)、表助手开始前的 404、批量审阅逐条的 table_missing 错误。

测试:test_catalog_schema_truncated 新增一条(205 张表、规则只收 200 张的合成库):范围外的表在表详情、表助手、
批量审阅里都说「探查范围外」,对象索引里也没有的说「没有表 …」。去掉修复回跑挂。
2026-10-17 08:28:54 +08:00
ThearchyHelios 7db9760121 用量计量拿到调用时实际用的模型名:评测汇总里助手用量不再记成 unknown
验收:评测汇总里助手用量的模型是 "unknown",写作用量的模型却是对的。写作用量来自运行的 llm.end(节点配置里
就有模型名);助手用量来自 providers/usage.Meter,它只从回调的 invocation_params 取 model / model_name,而演示
模型 MockChatModel 没有 _identifying_params,调用参数里只有 _type 和 stop,于是落到 UNKNOWN_MODEL。

改法两处:
- Meter:调用参数里取不到时,看 LangChain 照模型对象给回调的 metadata 里的 ls_model_name(调用时传的 model,
  否则对象上的 model / model_name),再取不到才看回复、才记 unknown。不认得调用参数的模型类也记得对。
- MockChatModel 补上 _identifying_params(model_name),和 OpenAI 兼容、Anthropic 一样在调用参数里报模型名。

测试(test_usage_meter):调用参数里不报模型名的模型、演示模型都按对象上的名字记;三种接入(OpenAI 兼容、
Anthropic、演示模型)由 factory 建出来,照 LangChain 回调时给的参数喂给计量,记的都是接入里配的模型 id,
调用参数里本身也带着;评测作答(eval/answer.Driver)走演示模型时助手用量记在 mock-fast 名下。两处修复都
去掉回跑挂 3 条,只留计量那处时挂「调用参数里本身带着」那一条。
2026-10-17 08:24:15 +08:00
ThearchyHelios caeab0fad6 SQL 差异说明认得同一列上的条件改写:说清「这次对 X 的条件是 …,口径是 …」
验收:口径 status = 'paid'、这次 status IN ('paid', 'refunded'),explain_sql_diff 只给兜底「两条 SQL 的写法不同,
未能归纳成具体差异」。原因是差异说明只说四类,同一列上两边各写一套条件(等号对 IN、取值集合不同、范围端点不同)
不在其中:_extra_filters 看到另一边也用了这一列就放弃。

改法:_Explainer 加 _rewrites,排在 _times 之后、_extra_filters 之前。同一张实表的同一列两边 WHERE 里都有条件、
至少一边有没配上的,就说「A 对 X 的条件是 …,B 是 …」;两边都只是取值集合、又有共同的取值时补「(这次多了
'refunded')」。两边都是时间条件时 aspect 是 time_range(FacetDiff 说明里早有这个方面、engine/compare 也按它
排序),否则是 filter 且 a、b 两边都有——dual_check_facts 对两边都有的 filter 本来就说「两边各有不同的筛选条件」
不写目录,ledger/stats、评测页的方面叫法都已认得这两个名字。

为了不说出不存在的不同,只在这些条件都满足时说:两边用到这一列的条件都只涉及这一列、比的是那一列本身;
按取值逐段算过两边留下的取值确实不同(_Range.compare 加 never_null:表结构里确定不为空的列不拿空值比;
x >= 100 AND x >= 50 对 x >= 100 这类冗余写法算出来一样,不说);不在 LEFT JOIN 可空一侧;WHERE 之外(ON、
HAVING、CASE、聚合参数)都没用到这一列(SUM(CASE WHEN status = 'paid' …) 配上 IN 可能和只筛 paid 一样);不是
认不准的时间窗口;没换表;有认不出列的条件时整段不说。

只改说明、不碰等价判定:说明只在已经判了不等价之后才跑。新测试把 _rewrites 换成什么都不做,逐对(语料全部
加几对改写、两种先后、给不给表结构)比等价与否和判定的级别,一字不差;IN 只有一个值、IN 列表去重排序照样判等价。
语料执行守门(意思一样的对不许说出具体不同)全过。

测试:test_sql_semantics 新增三组(各方言说两边、可能不影响结果的不说、等价判定不变);原来断言这两类只给
兜底的用例改成「算出来的值上换了值」(LOWER(status))仍只给兜底;test_sql_text_diff、test_dual_check_facts 跟上
(同一列上的时间范围现在是 time_range,照样不写目录;换了筛选的值不写目录)。去掉 _rewrites 的调用回跑,新测试
挂 5 条。

双盲复算的前端夹具 dual_check.json 里「说不清的」那个变体原来就是同一列上的时间范围,现在说得出了:换成套着
DATE() 的时间范围(认不出取值范围,仍只给兜底),test_dual_check_fixture 重新生成,check-verify 对应的说法和
高亮那一行的断言跟上。
2026-10-17 08:17:12 +08:00
ThearchyHelios 7f949cce92 取值观察:业务日期有未来值时不再误报量跌、缺天;「今天」统一按服务端所在时区
验收复现:业务日期最晚到明天、今天只写了几行,记出一条 volume_drop。原因有两层:
- 比较器 value_drift.daily() 取 last = max(days),只在 last >= today 时退一天;max 落在明天时退到的是今天这个
  还没写完的日子。改成最近一个整天 = min(有数据的最后一天, 昨天)。
- 按日行数的查询 day_volume_sql 只有下界,未来的日子也进了窗口。加上界:数到观察当天(含),第二天起不算
  (< 第二天,时间列上当天的时分秒照样算进来,文字存的日期按字符串比同样成立)。签名改成必须传 until。

缺天同样的问题也修了:今天以后的空日子不算缺天;以前那次观察只看到它自己那天(data.until)为止,免得它
窗口里未来的空日子被当成「以前就有过缺天」,挡住这一次真缺的天。断档遇到未来值时离今天是负数,只会报不出、
不会误报,观察本身照实记最新业务日期(目录里也照实给人看),不截未来值,写进了模块说明。

「今天」:原来窗口用 UTC 日期、比较器用 now.date()(也是 UTC),业务日期是本地日期,东八区凌晨 0–8 点会差
一天。统一成服务端所在时区(catalog_profile_observe.local_today,和待办汇总 notify.digest_at 同一个口径),
写进模块说明。

测试:test_value_drift 两条(未来值不算最近整天、未来空日子不算缺天且历史按 until 截);test_catalog_observe
两条(SQLite 真跑剖析任务,明天的行不进窗口、不记量跌;local_today 按 TZ 取本地日期)。去掉修复回跑都挂。
金样(tests/fixtures/dialect/*.sql)的 day_volume 一行随上界更新;scripts/dialect_it.py 加了一条「只数到观察
当天」的真库核对。
2026-10-17 07:59:19 +08:00
ThearchyHelios adfd783a07 合入下一轮第 4 波(收口,docs/design/next-round):口径召回(发布新版本后反查用过旧版的答案、按参数分组在同一份数据上重算两版之差、数变了才开召回待办、没变的只记账;目录事实与表结构变动标「依据有变」;版本用量与发布弹窗的召回预告、版本历史的召回批次);评测前端(导航「评测」、题集与加题、发起与进度、结果、对比与 A/B、口径命中与重复度、目录快照;定时页选目标种类;设置里的评测一组);口径账本前端(证据「口径」一步、问数据轮次摘要与出具 / 对账、对账反馈、可沉淀的问题、设置里的口径账本一组、比对结果轮询);目录前端(「变化」视图与处理、数据源角标、表详情的变化一节、定时维护、后台任务条);遗留:被驳回的那一步各处画「被驳回」、问数据建图用量不再是下限、比对结果实时、source.changed 与 ledger.recall 打开订阅。验收:后端全量 14284 通过;tsc、文案检查;本波相关的 21 项浏览器检查 7084 项全过;泄露扫描干净 2026-10-16 22:43:04 +08:00
ThearchyHelios ccf1fb21d2 check-all:第 4 波三项检查的耗时估计按实测改(只影响排队顺序) 2026-10-16 22:42:45 +08:00