Commit Graph

263 Commits

Author SHA1 Message Date
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 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 d4e68bd1a9 合入 W4-d 目录前端:数据目录的「变化」视图与处理动作、数据源角标、表详情「结构与取值变化」一节、数据源的定时维护(一条目标为数据源的定时任务)、后台任务条(探查、起草、剖析按服务端排队顺序);打开 source.changed 订阅;指名数据源卡片统一用 #source-<id> 2026-10-16 22:30:09 +08:00
ThearchyHelios d17a08bafb 合入 W4-c 账本前端:证据「口径」一步与数字上的小标记、问数据轮次摘要与按口径出具 / 对账、对账视图与反馈、可沉淀的问题、设置里的口径账本一组、比对结果按退避轮询;被驳回的那一步 node.failed 带 reason=rejected,画布、航迹、时间线、问数据过程都画「被驳回」 2026-10-16 22:29:02 +08:00
ThearchyHelios 13bd021219 合入 W4-b 评测前端:顶级导航「评测」(搭建者以上、⌥0)、题集与四种加题、发起评测与预估花费、评测详情与逐题结果、对比与 A/B、口径命中与重复度、目录快照;定时页选目标种类、认得数据源维护的触发记录;记录页的评测来源;设置里的评测一组;问数据建图用量不再是下限(读完模型的流再关);问题聚类的向量只取一次 2026-10-16 22:28:35 +08:00
ThearchyHelios b3dcddb52e 召回复核修正(后端):人工节点不再挡召回、读不出召回卡记失败、旧版不再接着算、团队渠道一次召回只推一条、目录核对按数据源合并排队;账本接口带上召回标记,对账结果可以反馈
- W4R-01:召回只查新版图里会不会写外部系统(apps/backfill 抽出 graph_external_write,graph_side_effect 复用它)。召回图只有两版
  的取数和口径卡,图里别处的人工节点、按流程使用的问题碰不到它;口径卡上游的人工节点 derive 本来就认作数据部分不能复用。
  「按新版重新出具」走完整的正式运行,仍按 graph_side_effect 全套拒绝。原来带审批的口径升版后一组都不召回,L6 对它们失效。
- W4R-09:收尾时召回卡读不出、或缺了某个比较的旧值 / 新值,照实记重算失败(读不出召回卡),原答案一个不动。原来两边都是空,
  按渲染比被当成「没变」,给每个原答案永久盖上「已核对,不影响」,收过尾之后再也不核对。
- W4R-12:这一版已经不是当前发布版(之后又发布了更新的版本)时不再发起召回、不给接着算,批次里写 continue_reason,接着算的
  接口 409 带同一句。同启动时接着做只认当前发布版:新版本的召回覆盖更早的所有版本,再按旧版开「口径升到 vN」的待办只是添乱。
- W4R-13:开登录时一组答案几个主人就几条待办,全局渠道却按这一批(recall.batch_of_key:同一次召回、同一个来由)只在最早开的
  那条上推一次,答案、待办按这一批汇总,几条的链到待办箱;别的几条连 task.opened 也不再推全局渠道。个人渠道照旧每人一条。
- W4R-11:目录审阅之后的核对按数据源排队、批量审阅的几张表合成一遍(catalog_pass,同一数据源一把锁);查询快照按数据源在库里
  筛、按运行结束时间分页、每张表凑够 BASIS_MAX_RUNS 次运行就停;冻结的表结构快照这一遍里按工件 id 只读一次。原来批量审阅
  几十张表就同时起几十个后台任务,每个都全表扫查询快照、反复读同一份几 MB 的表结构快照。
- W4R-03(本包那一半):GET /api/runs/{id}/ledger 加 recall:checked(已按 vN 核对,不影响)、basis(依据有变:哪一种、几项、
  新出现几条错误级问题)、tasks(看的这个人手上说的是这个答案、还没处理的 recall 待办)。不放数、不放表名。几种状态都带。
- W4R-10(本包那一半):新事件 ledger.feedback(对账运行上的封存后事件),POST /api/ledger/answers/{run}/reconcile/{rid}/feedback
  {verdict: useful | unclear} 记反馈,同一个人改主意只算最后一次(app/ledger/feedback.py);账本接口的 reconciled 每一项带
  feedback {mine, useful, unclear};统计 E2 的 feedback 数到它,去掉「第 4 波接上」的占位说法。离线核对页、封存后事件摘要
  都认得这个事件。

测试:test_ledger_recall 新增七个用例、改了人工节点那一个(召回照算、重新出具照实拒绝);新增 test_ledger_feedback;
test_ledger_api 的 E2 断言跟上。每个修复都先写失败的测试、去掉修复回跑确认抓得住。后端全量 14237 通过。
2026-10-16 22:16:58 +08:00
ThearchyHelios 7f5578c094 定时任务页认得数据源维护:触发历史列出建了的探查、剖析任务和没执行的步骤;「去数据源页设置」改链 #source-<id>(复核 W4R-05、W4R-04)
W4R-05 为什么:数据源维护的一次触发建的是探查、剖析任务,没有运行;api/schedules._fire_outs 不交触发记录的 inputs,
FireRow 只认 run_id 和 eval_run_id,于是每一次建了任务的维护都写成「已发起 · 未发起运行」,和数据源页「定时维护」的
「最近一次」对不上;「立即运行」一次维护还弹「未能发起运行」。

- 后端 api/schedules._fire_outs:目标是数据源维护的触发另带 inputs({steps, jobs, skipped},app/data/source_maintenance
  记在触发上的那一份,和数据源页「定时维护」读的同一个形状);别的目标给 null(工作流触发的 inputs 是按模板渲染的工作流
  输入,触发历史不列)。触发历史、页头「上次触发」、列表、立即运行的答复都走它。test_schedules_api 的 FIRE_KEYS 加 inputs。
- 前端 FireHistory:维护的那一行逐个列建了的任务(步骤、任务种类、编号、表数),探查链到数据源页那张卡片、剖析链到数据
  目录里那个任务的进度(?pjob=,同「最近一次」);没执行的步骤逐条写原因,服务端连成一句的原因不再重复;没建任务写
  「没有建任务」(刚触发、任务还在建写「正在建维护任务」),不写「未发起运行」。
- 立即运行(parts.runNow):维护开始了说「已开始维护」、详情逐行写建了什么;跳过照实说上一次维护还没做完;没开始说
  「没有开始维护」和原因。状态小标签的悬停同理。
- types.ts 加 ScheduleMaintenanceStep、ScheduleMaintenanceInputs(和 W4-d 的 MaintenanceFireInputs 结构一致,合并后
  交叉类型照样过 tsc);叫法进 SCHEDULE_TEXT(步骤叫法同服务端 STEP_LABEL,测试逐个对上)。

W4R-04 为什么:维护定时详情里「去数据源页设置」链到 /data/databases?source=<id>,数据源页(W4-d)只认 #source-<id>
(目录页后台任务条「查看」的约定),点过去落在列表顶上。统一成 #source-<id>:链接都经 model.sourceCardPath 出,
探查任务的链接同用它。

检查:check-schedules 新增「纯函数:数据源维护链到哪里、建了什么怎么写」「数据源维护的触发历史」两段(假接口:四种
维护触发、立即运行开始与跳过),改「链到数据源页」的断言;394 项全过。去掉 FireRow 的维护分支、链接改回 ?source= 回跑:
4 项失败。后端新文件 tests/test_maintenance_fire_history.py(立即运行一次维护后触发历史、页头、列表都带 inputs;工作流
触发不列渲染的输入;步骤和叫法前后端对上)。
2026-10-16 22:02:09 +08:00
ThearchyHelios d0082b4645 问题聚类的向量只取一次:评测页「口径命中与重复度」不再按窗口把问题重发给向量模型(复核 W4R-14)
为什么:这个页签一次打开,/api/evals/signals 按窗口调 compute_stats(缺省 6 个窗口,每个都聚一次问题),页面另调
/api/ledger/stats,一共聚 8 遍;chat/questions.cluster_questions 每遍都把窗口里的问题原文整批发给 embed_texts,
一次里重复的问法也照发。配了远程向量模型时每打开一次就是几百到几千次向量调用,慢、花钱。复现:本地 3 句问题各问 3 次,
一次打开每句被取了 9 次向量。

做法(改在全站唯一的取问题、聚类处 app/chat/questions.py,不动 ledger/stats 的统计口径):
- 问题原文 → 向量在进程里留一份(_vectors,最多 VECTOR_CACHE_MAX=1000 句、最近用到的留着),同一句话只取一次,
  一次里重复的问法去重后只发一句;/signals、/ledger/stats、认证队列、治理页的探索问题聚类都走它;
- 缓存认「哪一个 embedder、哪一个取向量的函数」算的:设置页换了向量模型(configure 换了对象)整份丢掉,不把两个
  向量空间的坐标混着比;取的这一会儿换了的,这一批照用、不留;
- 取向量按事件循环串行(每个循环一把锁,同项目里别处的写法):页面并行取的两个接口不把同一批问题各发一遍。

测试:test_evals_ui_api 新增「一次打开每句问题只取一次向量、再打开一次都不取、两个接口并行也一样」;
新文件 tests/test_question_vectors.py(去重、换模型丢缓存、取到一半换模型、上限与最近使用、并行只发一批)。
去掉缓存回跑:每句 9 次;去掉换模型的核对:2 条失败;去掉共用的锁:并行那条失败。
2026-10-16 21:41:30 +08:00
ThearchyHelios 45c1ede402 评测前端:顶级导航「评测」、题集与加题、发起评测、评测详情、口径命中与重复度、目录快照;定时页选目标种类;记录页评测来源;设置里评测一组
为什么:前三波评测只有接口,这一波是收口——每样能力都要有界面、能走通(细案 01 第 18 项、第 15 项定时的前端部分,评测-D8)。
界面层不放逻辑:比率、结论、趋势、分档、能不能做都由服务端给(上一次提交在 api/evals.py 补的 latest、trend、actions、
preview、signals、逐题花费),界面只显示。

- 导航:「评测」排在设置前面、⌥0,搭建者以上可见(使用者的页面表在 visiblePages 里收窄,手输地址被送回应用首页)。导航从 9 项
  变 10 项,在 600、520、480 像素高的窗口里重新量过:≤520px 那档的导航项从 32px 收到 30px(17px 图标 + 2px + 11px 字),
  不然 480 高时遥测点超出视口 12px;App.tsx 的说明跟着改。
- 评测页 pages/evals/*(类型 typesEval.ts、接口 api/evals.ts、文案 lib/termsEval.ts 的 EVAL_TEXT):题集列表(最近一次、同一
  变体的趋势、定时,没有定时给「设定时」带着题集去新建);新建、导入(先预览)、导出、归档、删除、题集设置(时区、默认变体——
  定时评测按它评、预算只填要改的项);题目表(启用、试算、编辑、删除),口径升版待处置的顶部提示和三选一处置(叫法由服务端给,
  没有缺省);加题四种方式:写 SQL 定期望(选数据源、写 SQL、预览、点选格 / 列 / 配对列、按维度找行、试算,遮罩列拒收的原话
  落在 SQL 下面)、选口径(工作流|口径卡|指标,不能复用的照列写原因,口径的输入按那一版的入口字段填)、从问数据运行认定
  (选运行、选查询、选取哪一列,用到遮罩列的查询不给认定)、期望没有数据或期望反问;可另加文字要点。数值期望不许人填:只点选
  取哪一格。
- 发起评测:评一次 / A/B 对照(两到三臂)/ 目录贡献对照(预设,可加「只用已核实和已确认的项」),变体有助手、写作模型、结论
  裁判、目录(当前 / 只用已核实的 / 关 / 快照)、重复次数;估算和发起是同一份请求体,项数、按上一次估的花费、生效的预算、今天
  已花照服务端写;可点名和哪一次比。
- 评测详情 /evals/runs/:id:进度和每题正在做什么、停止、只重试没做完的、按新规则重评、设为基线(按钮照服务端的 actions 摆);
  总览卡片(准确率和可追溯准确率带区间、花费、逐题 E1)和各项分布(结论、作答路径、出具档位、复核、结论裁判、引用率、查看、
  耗时、按角色的用量);逐题表(结论、期望和答案里的数、弱匹配、SQL 差在哪、查看、耗时、花费,展开看原因、去运行详情;
  ?case= 定位那道题);和上一次比(换基准、指纹差异、转移和归因);A/B 配对表(都对 / 只 A 对 / 只 B 对 / 都错、p 值)。
- 「口径命中与重复度」页签:E1、E3 这一窗口的值和档位、参考线、往前几个窗口的趋势(GET /api/evals/signals),明细直接调口径
  账本的 GET /api/ledger/stats(总方案 X2);成员不少于 3 的问题类能加入题集(带着问法去加题)、提取模板(从最近一次的运行提取,
  去画布)。目录快照页签:拍当前目录、影子起草(先估算再开始,能停)。
- 定时页:新建时先选定时什么(工作流 / 评测题集;数据源维护在数据源页建),评测题集的表单(pages/schedules/TargetEditor.tsx:名称、题集、
  FreqPicker、补发宽限,建题集目标用 ScheduleTargetCreate),评测题集的定时在详情里能改名称、频率、时区、补发宽限(SchedulePatch,
  只交改了的、带版本);数据源维护的定时只读、链到数据源页,第 3 波「只能查看,不能修改」那句改成说清在数据源页设置;触发历史里
  的评测链到评测详情。纯函数 targetFormOf / targetCreateBody / targetPatchBody / targetProblems 放 model.ts。
- 记录页:评测运行旁的「评测」标记写是哪一步(作答、算期望……),点了回到评测里那道题;运行详情页头有键盘到得了的链接
  (pages/runs/EvalSource.tsx)。
- 设置:偏好设置里「评测」一节(pages/EvalSettings.tsx,SettingsPage 里加一行):四项预算缺省、保留几次、缺省开不开结论裁判、
  今天已花;自己存、只交 eval 一组,取值范围只在服务端判。
- 问数据用量(评测-D14 的界面一半):复核慢过问数据页的等待上限时,答案照原样先交出去,复核那一次的用量晚到了照记进这一轮、
  再落一次库(以前直接丢掉);开发期 window.__chatReviewWait 让检查脚本把上限调短。

检查:check-evals.mjs 换成真检查(假接口加夹具,写请求在浏览器层拦下并断言请求体,98 项);check-shell 导航十项、⌥0、三种窗口高;
check-schedules 目标种类(纯函数和页面);check-chat 用量那一段加复核晚到。
2026-10-16 20:38:07 +08:00
ThearchyHelios 77fc0efa10 口径召回后端(W4-a):发布新版本后反查用过旧版的答案、按参数分组在同一份数据上重算两版之差,数真变了才开 recall 待办
为什么:口径升版是被迫处置的事件(02 做法 7、拍板 L6),已交付的答案以前没人追;目录事实被驳回、表结构变了也没有地方标。

- ledger/uses.py(新):反查旧版本被谁用过——比对工件里等价已证明的问数回答(含轮次、对话主人)、W 的正式运行(定时任务、
  应用各取最近 4 期)、固定了它的工作流版本发起的正式运行、固定这一版的应用、导出审计(导出人也是主人);预告、召回、
  版本用量共用这一份
- ledger/recall.py(新):计划按(旧版本、新版本、input_key)去重成组,从已有召回运行重算(幂等、不建任务表);每组一次
  召回运行(origin_kind=recall,探索运行),派生图经 derive.recall_graph(新旧两版取数和口径卡带前缀、召回卡里算两版之差);
  不能重算照实记原因(数据部分不能复用、新版没这张卡、入口参数不齐、图里有人工节点或写入);每次发布最多 recall_limit 组,
  超出的手动接着算;收尾时按渲染的旧值新值判数变没变:变了开 recall 待办(开登录按主人各一条、关登录派给任何人)、推
  ledger.recall,没变在原答案上记 ledger.recalled;待办「按新版重新出具」走 issue.check_target 同一套判法、「知道了」
- 目录事实被驳回或修改(审阅、人工编辑、保存提案、批量审阅之后):冻结目录里看得到这一项的探索运行记 ledger.basis_changed,
  按新目录多出错误级 SQL 检查问题的才开待办;表结构变动消费 source_changes(游标、处理时改了目录的当场交过去),标依据的
  表结构已变,受影响的口径试算一次,算不出来才通知发布人
- 发布钩子在 commit_publish 提交之后排队(自动的几样和调度循环同一个开关),GET /api/workflows/{id}/recall-preview 给发布
  弹窗预告;GET/POST /api/ledger/recall/{wf}/{v}[/continue] 给版本历史看批次、接着算;版本用量加问数回答、导出、召回
- ledger.recall 打开订阅(全局、个人渠道),同一渠道只推一次,消息不放数;新事件 ledger.recalled、ledger.basis_changed
  登记为封存后追加的事件,可验证报告离线核对页认得;E4 每次发布召回了几组、数变了的比例
- runner 收尾时召回运行在后台收尾,启动时接着做没发完的批次
2026-10-16 19:52:03 +08:00
ThearchyHelios a43224fd9a 账本接口补几样界面要画的东西:对账的入口字段、配列选项、对账卡的分组,刚跑完未封存时答 pending
账本前端(第 4 波 W4-c)照「界面层不放逻辑」只照服务端给的画,这几样以前接口不给,界面只能自己猜:

- 「看差多少」那一项(reconcile.actions_of)带上口径的入口字段 fields(名字、必填、有没有缺省值,比对工件里记的那一份):
  核对参数的表单照它画,缺的参数也写得出是哪一项
- 认不出列怎么配(409 ledger_reconcile_needs_pairs)另带 options:这次结果里是数、没遮罩的列,口径卡上的指标(id、名字、
  单位);ReconcileRefused 加 extra,api/ledger 的对账接口把它并进返回体(CodedHTTPException 的 extra,改一行)
- 对过的账(reconciled_view)带 card 和 groups:每一组对照的这次 / 口径 / 差值 / 相对差各是哪个指标、这一组怎么称呼
  (从对账运行的图里读,不按命名规则拼),界面按 /evidence/metrics/{card}/{指标} 取值、三个数各自点得开
- GET /api/runs/{id}/ledger:runner 先发 run.finished、落库,再封存、再排比对,页面收到 run.finished 立刻来取时
  还没封存。成功结束、要比对的运行这时答 pending(不另排按需比对),不再说成「运行还没有封存,不比对」
- 测试:对账的三样(单行、多行),刚跑完未封存答 pending、不排按需比对,失败的照旧 ineligible
2026-10-16 19:46:03 +08:00
ThearchyHelios 56ca53ad35 问数据建图用量不再是下限;评测通知链到评测详情;评测接口补界面要的几样
用量(第 1 波遗留):查清楚了,丢的不是浏览器那边——问数据页一直读到 SSE 结束、usage 那一条收得到——而是服务端生成过程
(api/copilot_gen._iter_ops)读到 done、look、ask 那一行就关掉模型的流,流末尾那一段才报的用量(Anthropic 的输出 token、
OpenAI 兼容系的 stream_usage)一次都拿不到,每次建图都记成「估不出」,这一轮的花费多半是下限。改成交出那一行之后先把流读完
再关(_drain,内容丢掉):照约定那一行就是最后一行,读完只多收用量那一段;模型在那一行之后还往下写超过 DRAIN_CHARS 字闲话的
当场关流、照实记成没报全(不为闲话付钱,也不当成报全了)。评测的作答(app/eval/answer.Driver)走同一个 answer_ops,
用量跟着报全,和问数据页同一个口径。test_usage_meter:原先断言「输出 token 为 0、记成估不出」的那条改成报全;另加 look 后、
闲话超限、评测和问数据页收到同一份用量三条;去掉修复回跑,三条都抓得住。providers/usage.py 的说明跟着改。

通知:notify_eval 的链接从定时任务的触发历史改成这次评测的详情页 /evals/runs/{id}(评测页这一波上线);测试设上对外访问地址、
断言链接(原来的断言在没设地址时对空列表恒真)。

评测接口(只加不改,app/api/evals.py):题集列表和详情带 latest(最近一次评测)、trend(同一变体最近几次评完的准确率,旧的
在前,换了模型、目录的不连成一条线)、schedules(目标是它的定时任务);评测详情带 actions(停止、只重试没做完的、重新评分、
钉住 / 取消基线能不能做,和接口拒收的条件同一份)和没做完的项数;POST /suites/{id}/cases/preview 写 SQL 定期望的预览
(同期望的 SQL 探针,交回列和前 50 行,遮罩列照保存时那一道拒收、没过守卫的指明是哪一格,都不回 422);GET /signals
「口径命中与重复度」的判据和分档、窗口趋势(数只出自 ledger/stats.compute_stats)、成员不少于 3 的问题类(只出自
chat/questions,评测运行不进)。为什么放服务端:界面层不放逻辑,统计、能不能做都由服务端给。tests/test_evals_ui_api.py。
2026-10-16 19:41:57 +08:00
ThearchyHelios 238330b9c1 W4-d 后端:给目录前端的变化、观察、后台任务、定时维护接口补齐界面要的几样;打开 source.changed 的订阅
第 3 波的变化接口只给了 agentlab-change/1 的原样,界面要的几样(能不能做、依据什么、现在怎样)如果让前端自己判,就违背
「界面层不放逻辑」。这一提交由服务端给出,界面只照写:

- 变化列表、单条、处理的回答里每条另带 category(结构 / 取值)、basis_text(依据的口径,照观察时记下的写整表、窗口、块采样、
  按存储顺序的前 N 行)、actions(现在能做哪几个处理动作,做不了的写原因:剖析没开、表结构里没有了、影响面里没有目录项)、
  job(处理时建的任务,界面能点进进度)、影响面里每个目录项现在的状态(清理过、改过的照实)。召回拉的 list_changes 不带这些。
- 列表能按 category 筛(和 kind 一起给时取交集,一种都对不上就是空的,不是不筛),能按 status=handled 看处理过的(最近测到的
  在前:处理过的不再按要不要先处理排)。
- GET /api/datasources/upkeep:数据源列表一次取齐各源要处理的变化条数(卡片角标)和指向它的维护定时(频率、开没开、下一次)。
- GET /{id}/observations:表详情「结构与取值变化」一节的最近几次观察,各带口径;码值的占比、按日行数的合计由服务端算;现在
  被遮罩的列(观察时还没遮的)整组不给,遮罩在读的那一刻再核。
- GET /{id}/background-jobs:这个源进行中和排队的探查、起草、剖析,按同源队的先后排好(05 (g):排队顺序取服务端的,界面
  不自己排),各带 waiting_for。
- GET /{id}/maintenance:维护定时换成定时任务页同一个形状(改频率、开关照样带 version 走 PATCH),最近一次触发带 inputs
  (建了哪些任务、哪几步没执行和原因),另说调度开没开、这个源要不要维护(上传表格、记录表不要)。
- notify:source.changed 打开订阅(只推全局渠道,按源汇总不归哪个人),链接落到目录页的「变化」标签页。
  test_notify_events 里 W4-d 那一行加上;test_source_changes 里原先假设订阅没开的那句改成「没有渠道订阅时推不出去」。

data/source_changes.py(W4-a 的文件)只在末尾加了 available_actions、counts_by_source、list_handled 和 HANDLED;
data/catalog_profile_observe.py 末尾加了 key_text、recent。新测试 tests/test_source_upkeep.py。
2026-10-16 19:31:21 +08:00
ThearchyHelios 45fab21072 第 4 波契约骨架:频率选择抽成公共组件 components/FreqPicker.tsx(频率与时区的纯函数搬到 lib/freq.ts,pages/schedules/model.ts 原样再导出),定时维护和定时任务共用;前端类型加 ScheduleTargetCreate(建评测题集、数据源维护这两种非工作流目标),api.schedules.create 收它;设置 ledger 一组加 recall_limit(口径升版后每次最多重算几组,缺省 50,0 表示不自动重算);test_notify_events 的 PUSHED_THIS_ROUND 拆成一项一行、给 source.changed、ledger.recall 各留一个位置;check-all 预登记 catalog-changes、evals、ledger 三项检查(先放占位) 2026-10-16 19:09:47 +08:00
ThearchyHelios eb6e3c7ab7 合入 W3-d 邮件、待办汇总、分享链接:邮件渠道(SMTP 可选、口令加密、退信不追踪);每天的待办汇总与访问密钥到期提醒;分享链接(只存哈希、必设到期、只显示定时出数的视图、/s 页不进外壳、下载报告);「最近一次封存」按来源白名单认;使用者拿不到邮件渠道的收件地址;同站门户嵌整站的配置说明 2026-10-16 18:48:07 +08:00
ThearchyHelios 1c8ca4c3d7 合入 W3-c 评测执行:后台评测与无头作答、六种期望五种结论的评分、重评、结论裁判记评测账、统计(Wilson 区间、按角色用量)、对比与 A/B(McNemar)、目录贡献预设、预算与止损、清理;定时任务接题集目标,定时评测有退步 / 没有评完推全局渠道;定时页照实显示非工作流目标 2026-10-16 18:46:44 +08:00
ThearchyHelios defdb28e3f 合入 W3-b 口径账本闭环:caliber_from.nodes 改名映射;问数据的助手 use_caliber 选用已发布口径(服务端确定性内联实例化);按口径正式出具(自动只给问数据的答案)与口径对账;认证队列、固化成口径草稿、发布后补比;账本统计补上对账与认证转化;使用者的原始运行记录只给问数据的运行 2026-10-16 18:45:45 +08:00
ThearchyHelios 38d9082994 使用者的原始运行记录只给问数据的运行,对账运行经证据接口读(复核 M3)
复核发现:使用者(开登录后的 user 角色)发起的口径对账运行带 MASK_RESULTS、记在本人名下,run_events_readable 只看这两条,
于是他能经 /api/runs/{id}、/events、/graph 读到对账运行的原始图和事件,里面原样是口径取数节点的 SQL 模板。这一波正是以
「口径的 SQL 可能写着遮罩值」为由不让使用者在问数据里看口径、use_caliber,这条读法绕开了证据面板按遮罩换 SQL 的那一层。

做法:api/deps.run_events_readable 给 user 角色再加一条 origin_kind == "chat"——原始记录、事件、运行时的图、实时流只给他
自己的、查询结果在工具边界遮过的问数据运行(原来的说明里就写着「实际上满足的就是它自己的问数据运行」,这次照实收紧)。
对账运行他仍然能经账本和证据接口读(can_read_run 不变,那里展示的 SQL 按遮罩换过),对账的发起也不变。改了
api/deps.py 的这一个函数(没分给哪个包)和 ledger/reconcile 的模块说明。

测试:开账号密码登录,使用者对自己的问数据答案经接口发起对账,对账运行的 /api/runs/{id}、/events、/graph 都回 403,证据
接口 200;他自己的问数据运行这三样照旧 200。去掉这一条回跑,新用例失败;test_auth_access、test_personal_apps 照旧过。
2026-10-16 18:25:43 +08:00
ThearchyHelios 9e33c0f801 评测的两个通知事件照实叫「定时评测有退步」「定时评测没有评完」(复核 L3)
eval.regressed、eval.failed 只在定时发起的评测收尾时推(runner._conclude 只认 trigger == "schedule"),手动发起的那次人自己
在看、不推,这和细案 01 做法 15 把这两个事件放在定时一节里一致。可订阅时的叫法却是「评测有退步」「评测失败」,没写「定时」:
有人给全局渠道勾上「评测失败」、再手动发起一次评测并止损停下,渠道里收不到任何消息,界面上也没处说为什么。

- notify/service.EVENTS 和前端 NOTIFY_TEXT.eventLabel 同一句改成「定时评测有退步」「定时评测没有评完」(test_notify_events
  逐字对照两边)。eval.failed 不只是失败:到了上限、止损停下(partial)同样推,所以叫「没有评完」,和消息标题本来的说法一致。
- notify_eval 的消息标题也带上「定时」:定时评测「题集名」有退步 / 没有评完。
- 新测试:两个事件的叫法带「定时」;手动发起的评测止损停下,订阅了这两个事件的全局渠道收不到任何消息(去掉 trigger 的判断
  这一条就失败)。
- 模块说明、接口说明里引用旧叫法的几处一并改。
2026-10-16 18:23:03 +08:00
ThearchyHelios e5003a1d46 W3-d 复核 L2:使用者列通知渠道时拿不到全局、应用邮件渠道的收件地址
复核发现:GET /api/notify/channels 对使用者照样列全局、应用渠道(应用的通知里要挑渠道,只用名字和 id),而邮件渠道的
mail_to 给的是全部收件地址原文;channel_out 的说明却写着「搭建者以上才看得到」。Webhook 渠道只给主机名,邮件渠道多露了
一层:任何使用者都能拿到内部的收件名单。

做法:channel_out 加 recipients 参数,list_channels 按 _can_edit 的同一条规矩判(关登录时谁都能改,开了登录要搭建者
以上):改得了的照原样给 mail_to(改地址要整份重填),改不了的只给条数 mail_count。个人渠道照旧写发到自己的账号邮箱。
建、改渠道的返回本来就只给能改的人,照旧。说明同步改;前端 typesApp 的 NotifyChannelRow 加 mail_count(设置页的
通知只给使用者列个人渠道,界面上用不上,不加入口)。

测试(tests/test_notify_mail.py):开登录,使用者列渠道时全局、应用的邮件渠道照样在、没有 mail_to、只有条数,自己的个人
渠道照常;搭建者看得到地址。去掉修复回跑,这一项失败。
2026-10-16 18:20:52 +08:00
ThearchyHelios e65ac3e619 助手的节点手册:子工作流补上 workflow_version(口径账本做法 4 ⑤)
为什么:手册里子工作流只写了 {workflow_id, input},模型不知道能固定版本。补上写法,并说明问数据里不要拿子工作流去拼已发布的口径——子工作流里的数报告引用不到,组合口径走 use_caliber 的内联实例化。
2026-10-16 17:18:37 +08:00
ThearchyHelios 538799e70d 沉淀:认证队列、固化成口径草稿、发布后补比;账本统计补上对账与认证队列的转化(口径账本做法 8、10,拍板 L9)
为什么:同一类问题被反复问、Agent 反复写出同一个形状的 SQL,就该沉淀成一份口径;以前 from-run 保留 Agent,沉淀出的模板没有固定 SQL,以后永远比对不上。第 2 波的统计在 E2 反馈、E3 认证队列上照实写了「目前没有数据」,这一波补上能补的。

做了什么(新增 ledger/certify.py):
- GET /api/ledger/certify-queue(搭建者):确定性重复(问数据运行里被报告引用、没对上口径的查询,按数据源 + normalize_sql 文本分组,≥3 次运行且来自 ≥2 段对话;跨运行变化的字面量是参数候选,展示的形状不带取值)、问题聚类(app/chat/questions 全站唯一的那份)、被反复用的答案(导出 ≥2 次用第 2 波的 report.exported 审计、做成过应用看蓝图来源、在画布里打开或提取过看工作流 origin)。每项给代表问题、轮次、相关口径、是否已固化。
- POST /api/ledger/certify-queue/dismiss:「不是口径」写审计 ledger.certify.dismiss,之后不再进队。
- 固化成口径草稿(from-run 加 freeze、peers 后转调 certify.freeze):报告引用的查询变成固定 SQL 的取数节点,跨轮变化的字面量换成 '{{ input.pN }}'(缺省值取代表运行的),单行结果的数值列成口径卡指标,报告核对规则按受管要求写、契约 report_from + metrics_from + required(多行加 cells);过 validate_graph 和受管门禁。新工作流 origin.sources 记来源问题。
- 发布之后补比:api/workflows.commit_publish 提交后调 certify.after_publish,来源运行按新口径补比一次(新的 trigger=certified,界面写「按之后发布的口径回看」,时间线和账本接口同一句)。
- 统计:E2 加对过账的答案数、参数经人核对的、指标由人选的次数(反馈照实写第 4 波);E3 认证队列的转化(固化、发布、不是口径)。

测试:tests/test_ledger_certify.py(参数推断与模板、三种信号进队、门槛、不是口径、固化草稿过受管门禁、发布后来源运行补比成等价已证明并出队、再问标等价已证明、统计)。
2026-10-16 17:06:41 +08:00
ThearchyHelios df6c3ce10d 定时页照实显示非工作流目标;评测停止时几臂一起收尾;工作流题的测试
为什么:这一波后端开始接受评测题集、数据源维护这两种定时目标(总方案 X4),定时页要能把它们照实列出来、点进去
不报错;界面上不能有点了没用的入口。

- 定时页列表:非工作流目标写种类(评测题集 / 数据源维护)和目标名,不标运行类别,「编辑」写成「查看」
- 详情:非工作流目标的设置只读地列出名称、目标、频率、时区、补发宽限,不出工作流那张表单(选工作流、输入模板、
  Webhook 对它们不适用);页头的启用、立即运行、删除和触发历史照常
- 立即运行发起的是一次评测时说「已发起一次评测」(以前会落到「未能发起运行」);触发历史写评测的编号
- types.ts:Schedule 加 target_kind / target_id / target_label / target_name / target_exists,触发记录加 eval_run_id;
  文案进 SCHEDULE_TEXT
- check-schedules 加一段「目标不是工作流的定时任务」
- runner:还没准备好就被停下、出错时,按 pair_id 找全几臂一起收尾
- 测试:工作流题按钉住版本跑探索运行(不发正式运行、不建图);钉住基线的接口
- 后端几句给人看的话按文案规范改(不用「跑」「钉」)
2026-10-16 17:04:05 +08:00
ThearchyHelios 906ec072ce 按口径正式出具与口径对账(口径账本做法 5、6,拍板 L1、L5)
为什么:问数据的答案整题等价于已发布口径时,要能按那一版发起一次完整走过门禁的正式运行(路径一、二a);只是相关、不等价时,要能看「差多少」(路径二b)。两条路都不能让客户端冒充来源、自己传参数,也不能在 Python 里算差值。

正式出具(新增 ledger/issue.py):
- POST /api/ledger/answers/{run_id}/issue {workflow_id, version}:参数只从比对工件的绑定取,补入口缺省值再过 apps/forms 校验;经 launch_run 发起正式运行,来源 ledger_issue,origin 记 source_run、conversation_id、turn_id、binding_hash、口径和认法、是否自动;按 expect_hash 钉 graph_hash。
- 不能出具照实写原因:整题不等价、点名别的口径、版本退役或没发布、图里有人工节点或写入(apps/backfill._side_effect 抽成共用的 graph_side_effect)、缺入口字段、超过每日上限、权限(L5:使用者只能按他有权使用的应用固定着的版本出具,复用应用授权,origin 记 granted_by_app;别的写「请搭建者出具」)。
- 自动出具(L1):比对完(只在运行一封存就比的那一次)整题独立等价、版本受管、设置开着、当天没超上限才自动;助手选用的只给一键。不自动的原因写进 actions 的 auto_reason。
- 同一运行、同一口径、同一组参数只发起一次(input_key 去重),进程内按(运行、工作流、版本)串行。
- 设置加 ledger 一组:自动出具开关、每日上限、比对时限(ledger/match 读它)。

口径对账(新增 ledger/reconcile.py,derive 扩 reconcile_graph / ours_block / assemble 的出口契约):
- POST /api/ledger/answers/{run_id}/reconcile:按那一版确定性派生只读图(这次的取数 ours_q1 → 口径的取数 → 口径卡 → 对账卡,出口契约 metrics_from,不加报告、不调模型),来源 reconcile 的探索运行;对账卡算这次、口径、差值、相对差。
- 参数是启发得来的、不全、含遮罩值时先让人核对(needs_check,没核对的 409);指标式不是取数一格、差异有兜底时要人选对照哪个指标,选择记进 origin。
- 多行结果:被引用的行按维度列经合并查询 LEFT JOIN 对齐、按引用先后排,最多 10 行;维度是遮罩列、合并键不唯一照实拒绝。使用者发起带 MASK_RESULTS / READ_ONLY,搭建者也带 READ_ONLY。
- GET /api/runs/{id}/ledger 的 actions 补上出具、对账两类,issued、reconciled 列出发起过的运行。

测试:tests/test_ledger_issue.py(自动出具、只给一键的几种、拒绝原因、使用者的应用授权、并发只发一次、设置)、tests/test_ledger_reconcile.py(核对参数、派生图确定与契约、差值和相对差、出处链、要人选列、多行对齐、遮罩维度和重复键拒绝、使用者只读遮罩);第 2 波 test_ledger_api 的 actions 断言按这一波改。
2026-10-16 16:55:06 +08:00
ThearchyHelios e29adf80d0 评测执行的测试:执行、评分、统计与对比、清理、定时与通知
为什么:照细案 01 的测试清单把这一波的行为钉住,顺带修了跑测试时撞出来的两处。

- test_eval_runner:后台排队、并发不超过 max_parallel 也不超过全局并发的四分之一、同一题集同时只能一次、停止后只重做
  没做完的(评完的不动)、重启标中断不续跑、单次金额上限触顶不再开新题、评测每日账单独记(不占裁判、核查的额度)、
  连续 3 题助手调用失败止损、每题超时取消正在跑的运行、停在审批上的作答取消掉(评测不代批)、接口的估算与进度、设置里
  eval 一组和 /settings/eval/spend
- test_eval_score:五种结论、写出精度和百分数、维度配对与弱匹配、多行 / 名单 / 没有数据 / 反问、路径和期望先于数字、
  dual 两版、允许的相对误差和双盲复算是同一个函数;读封存的答案、逐题 E1 用账本的比对函数;重评只读封存、不调模型
- test_eval_stats:Wilson、精确 McNemar、P50/P90 和手算对照;总表的各项;转移与四种归因;指纹差异;配对表;选基准
- test_eval_cleanup:保留规则;forget 只删一条引用都不剩的文件;清理后总表还在、结果和底下的运行删掉、共用的工件留着
- test_eval_schedule:定时接口收题集(拒工作流、输入模板、Webhook)、到点建一次评测并写 eval_run_id、重叠跳过、错过的按
  宽限补;有退步推「评测有退步」、没评完推「评测失败」,消息里没有期望值和答案里的数
- test_eval_e2e 补:开评拍目录快照、记表结构前后;打开裁判时结论裁判走按需裁判那条路、花费记在评测账上,文字要点单列
- 修:期望反问的题在建图、运行失败(含超时)时把原因写全;改非工作流目标的定时任务时提交后不再读过期对象
2026-10-16 16:49:47 +08:00
ThearchyHelios f5c94cf12d W3-a 变化记录的接口与处理、定时维护(next-round 03 B3、B4)
变化没有统一的记录:进不了审阅,发不了通知,口径账本的召回也没有可以订阅的接口;目录只能手动探查、手动剖析。现在:

- api/source_changes.py(骨架已在 datasources 通配之前登记,搭建者可用,访问密钥不放):GET /api/datasources/{id}/changes
  (缺省开着的和知道了的,按「使用次数权重 × 严重程度」排,带各级别计数)、GET /changes/{change_id}、POST
  /changes/{change_id}/resolve。处理动作落到目录已有的写入接口:清理目录里说到被删的列的项(整份写、乐观锁)、加入码值
  (apply_patch 配 _merge_codes,含义待填写,依据写变化编号)、重新起草这张表(模型用不了时只按表结构起草)、重新剖析、
  知道了、误报(必须写理由);每个动作写 resolution,动了目录的记目录版本,建了任务的记任务编号。
- data/source_changes.py 给召回:list_changes(按 seq 游标拉)、affecting(按源和表反查)、resolve、triggers_recall(只认
  结构类、处理时改了目录的、触及有效记录条件的新码值)。
- 通知 source.changed 的发送函数 notify/service.notify_source_changed:只推还要人看的「要处理」级变化,按源汇总,消息里
  只有源名、各级别条数和链接(D9);探查、剖析任务结束时有新的要处理变化就调。这一行的订阅范围第 4 波有了界面才打开,
  现在推不出去(照实)。
- 定时维护(data/source_maintenance.py,X4:就是一条 target_kind=source_maintenance 的定时,不另建表):fire 按顺序建
  探查任务 → 剖析任务(范围 needed / observe / maintenance 按选的步骤),任务上记 trigger=schedule、fire_at、schedule_id;
  剖析等探查进了同源队才建,靠 SourceLanes 串行;剖析没开、数据源停用 / 不存在 / 是上传表格时照实记没执行,同源还有
  探查或剖析在做时记跳过;触发记录的 inputs 写明建了哪几个任务、哪几步没执行。check_target 核对目标。维护步骤存在
  options.catalog_maintenance,经 GET / PUT /api/datasources/{id}/maintenance 改,数据源表单不显示它(api/datasources.py
  的 _MANAGED_OPTIONS 加了这一个键)。

测试:test_source_changes(列表与计数、误报要理由、处理过的 409、清理目录与加入码值记目录版本、剖析没开照实 422、
去重、游标拉取与反查、召回触发、通知只有源名条数链接)、test_source_maintenance(按顺序串行、trigger 与 fire_at、
剖析没开记未执行、停用 / 不存在 / 忙、调度循环只补最近一次且积压只记一条、步骤接口与核对目标)。
2026-10-16 16:48:40 +08:00
ThearchyHelios f887cdc30a W3-d 邮件、待办汇总、分享链接的界面
- 设置 → 通知:渠道多一种格式「邮件」,邮件服务没启用时不列;个人邮件渠道不填地址,写「发到账号邮箱 …」(账号没有邮箱照实写
  发不出去),全局、应用的填收件地址;投递记录里邮件成功写「已交给邮件服务器」、悬停说明退信不追踪;新增「邮件服务」一节
  (管理员改、搭建者只看:口令留空不改、可清除,发测试邮件要先保存)和「每天的待办汇总」的发送时间
- 设置 → 偏好设置的「应用」一节:「允许建分享链接」开关(缺省关,写明关掉时已有的一并打不开)
- 应用编辑 → 岗位与授权:分享链接的管理(选页面、始终最新 / 固定这一期、有效期 1–90 天、允许嵌入;链接只显示这一次;
  列出状态、到期、打开次数,撤销先确认);开关关着时不给新建,已有的照列能撤销;个人应用写明不能分享
- /s 分享页:不进外壳(App 在 /s 上整个跳过认人、取目录、心跳、读主题,请求层在 /s 上也不送去登录页),key 从 # 后面读、
  放进请求头;同一个 ViewRenderer 渲染、操作全禁用;印章常显(「数据的来历」逐个列视图的印章);点数字说明出处在下载的
  报告里;页脚「只读 · 由 X 于 … 分享 · … 到期」;撤销、到期、关了开关、链接不完整按服务端的话写明;没允许嵌入的分享在别的
  页面的 iframe 里不显示内容、给「在新窗口打开」
- ViewRenderer 加 onEvidence:点数字交给调用方(分享页),不开在线证据面板
- check-copy 加「快照链接」一条(总方案 X10:写「分享链接」),带自检
2026-10-16 16:44:06 +08:00
ThearchyHelios 2371e75f17 评测执行的主干:后台评测、无头作答、评分、统计、对比与 A/B、清理(下一轮第 3 波 W3-c 第一段)
为什么:第 2 波只有题集和期望答案的来源,评测本身跑不起来。这一段把细案 01 做法 9–12、16、17 的后端接上,
量的是用户在问数据页拿到的同一轮,而不是另抄一份编排。

- eval/answer.py:无头作答,串起 answer_ops → chat/turn 的决定 → launch_run(origin=eval,带目录视图、只读、遮罩)
  → 等终态(停在审批的取消、记「评测不代批」;驳回记路径「被驳回」)→ review_of_run → 按同一规则重来一次;
  工作流题按钉住版本的图跑探索运行(不发正式运行,免得被 pick_base 和应用视图取到)。用量按角色记
- eval/runner.py:用 core/job_runtime,一次评测一个任务,题 × 重复 × 臂一项;A/B 几臂共用期望、同一题同时开跑;
  开评拍目录快照、记上传表格版本和表结构哈希的前后;并发不超过 max_parallel 和全局并发的四分之一;每题时限;
  评测单独一本账(开跑前估算、每题预留后找补、触顶不再开新题);止损;能停、只重试没做完的、重启标中断不续跑
- eval/score.py:六种期望、五种结论(量纲不同单列)、维度配对与弱匹配、路径不对直接不通过、口径 dual 两版都报;
  逐题 E1 用口径账本的 compare_queries(总方案 X2,不另写 signals);SCORER_VERSION 可重评(eval/rescore.py,只读封存)
- eval/judging.py:结论裁判走按需裁判同一条路(evidence._judge_now),文字要点由裁判判、单列不计准确率;花费经
  judge.charged_to 记在评测那本账上(engine/judge.py 只加了一个上下文变量和取账本的一处)
- eval/stats.py、eval/compare.py:准确率与可追溯准确率 + Wilson 区间、各项分布、P50/P90、按角色成本、引用率、逐题 E1;
  选基准、逐题转移与归因、指纹差异、A/B 配对表与精确 McNemar;「目录贡献」预设对照(当前 vs 关,可加只用已核实)
- eval/cleanup.py + core/artifact_store.forget:保留最近 N 次 + 钉住的基线 + A/B 另几臂,删结果、明细工件、底下的运行和
  引用行,工件文件一条引用都不剩才删;总表永久保留
- engine/tolerance.py:双盲复算的 _agree / _delta_pct 原样搬来共用(dual_check 改 import,行为不变)
- 设置加 eval 一组(builder 读、admin 写)和 GET /api/settings/eval/spend;题集预算在全站缺省上覆盖,每日上限只能往下收
- api/evals.py:发起(单臂 / A/B / 目录贡献预设)、估算、进度、逐题、停止、重试、重评、钉基线、和上一次比、配对表、清理
- main.py 的 lifespan:影子起草和评测的启动整理、关停(评测先于运行停,停时取消自己发起的运行)
- 测试:test_eval_e2e(dual_check_shop + 剧本模型:按支付日 330 通过且 SQL 等价,按下单日 310 不通过、差在 time;
  目录对照 1:0;和上一次比找到退步、归因做法变);通知事件、定时接口形状的老测试按新事件和新字段更新
2026-10-16 16:38:09 +08:00
ThearchyHelios 4f4ad48ad7 组合式问数:问数据的助手可以 use_caliber 选用已发布口径,服务端确定性内联实例化(口径账本做法 4,拍板 L3)
为什么:问数据以前看不到口径,也没有组合机制;子图做骨架的路走不通(子图证据不交回父图、使用者用不了子工作流)。改成助手只选口径、填参数,取数节点由服务端按固定版本照抄,证据照常封存、比对照常复证。

做了什么:
- 问数据的查看对象加 caliber(look/calibers 的 compose 模式):另给每张卡的入口字段(类型、必填、可选值、格式、缺省值)和「能不能组合」(ledger/derive.load_pinned 同一个认法),能组合的写出 use_caliber 的写法;仍不给 SQL。只给搭建者及以上:使用者的图里看得到拷进来的取数 SQL。
- 新增 ledger/compose.py:use_caliber {id, workflow_id, version, card, metrics, inputs} 经 derive.caliber_block(扩了 card_id、inputs 两项)实例化——取数节点照抄、id 加前缀、模板按参数渲染成字面 SQL,口径卡 id 就是操作 id、caliber_from 带改名映射。参数按入口字段过 apps/forms 校验,写进 SQL 引号里的不能带引号、裸写的只能是数;只能选这一轮看到的那一版、能组合的卡、卡上的指标;不合规拒收。
- GraphApplier 落地 use_caliber:节点、连线记进 seen(兜底重放照样在),入口连到第一个取数节点;转给界面的只有那一条操作带 result;拒收的原因经自查交回模型(GraphChecker.issues)。
- 提示:协议多一段 USE_CALIBER_PROMPT;LOOK_WHEN 里问数据那一条按细案改写(只在能选用口径时换)。运行时写 SQL 的 Agent 不看口径(L2)。
- 比对器认出助手选用的查询(从封存的图里读 caliber_from.nodes):证明了的标 composed「助手选用」,没能证明的照常是相关口径并说明;标签 how=composed,指标标签记取自哪几条查询;比对算法版本升到 2。
- 前端:问数据过程里 use_caliber 照实显示一行(选用了哪一版、生成几个节点;拒收写原因),节点数算进「新增几个步骤」;文案进 LEDGER_TEXT.use;check-decode 加一节。

测试:tests/test_ledger_compose.py(实例化确定性、各种拒收、查看文字不带 SQL、提示切换、落地与接线、拒收交回模型、剧本模型走一遍生成再真跑出 composed 标签、使用者不给口径)。
2026-10-16 16:35:28 +08:00
ThearchyHelios f759c5c82d W3-d 分享链接(后端):只读、不用登录、必设到期、只显示定时出数的视图
为什么做(04 细案 C2,集成-I6):会话 cookie 是 SameSite=Lax,跨站的 iframe 里带不上,想把看板放进别的门户只能每人开账号且
必须同站;分享链接是跨站嵌入唯一走得通的办法。它把数据交出了授权范围,所以每一步往窄处取:
- view_shares(db/models_apps.ViewShare):只存 key 的 sha256 和开头 6 位;latest 跟着最近一次封存走,fixed 存建分享那一刻
  每个视图的运行 id、按建分享时的应用版本解析;到期必填;打开只计数
- POST / GET / DELETE /api/apps/{id}/shares:要应用的 manage,建要管理员开了 apps.share_links(缺省关);个人应用、没发布、
  归档的不能分享;这一页没有最近封存的视图不收;到期最长 90 天;key 只在建的时候交回一次(/s#<key>);建、撤销写审计
- GET /api/shares/page、/api/shares/report.html 是公开路由(中间件把 /api/shares/ 当公开前缀),只认请求头
  X-AgentLab-Share,地址里带的 key 不认;撤销、到期回 410 写明;开关关掉时已有的一并 403;应用归档、页面没了 410
- apps/resolve.resolve_page 加 share 旁路:只解析 latest_sealed 视图(fixed 用存下的运行,趋势也停在那一期),按需、实例、
  探索视图写明原因、从不发起运行(auto 的也不);流程组件和报告换成写明原因的说明块;页面缓存键另加分享的标识;out 另给
  各视图取到的运行和可下载的报告
- 下载报告只认这条分享此刻解析出来的运行,内部就是 export_report_html(同样遮过、照常记导出审计,导出人记成不冒充任何人的
  分享身份)
- 分享接口都不放给访问密钥(不在 KEY_ROUTES,门禁 403)

安全头:/s 用 AGENTLAB_EMBED_ORIGINS 和 no-referrer(第 1 波 web_security 预留的规则,这次在真实页面托管下测)。C3 同站门户
嵌整站只做配置和文档:写进 web_security 的模块说明和 deploy/agentlab.env 的「嵌入」一节,「分享链接还没有上线」那句改掉。
2026-10-16 16:30:42 +08:00
ThearchyHelios 8d59ca1581 W3-a 表结构变化:前后差异、影响面、结构版本与 schema.drift(next-round 03 B1)
以前重新探查是整份替换表结构、不比前后:删了一列,那一列上人工确认过的目录项、写死这一列的已发布模板都收不到提醒,
正式运行要到查询那一步才报错。现在:

- data/schema_diff.py:纯函数,同一张表前后两次的结构比出变化(加列、删列、类型、可空、注释、主键与唯一约束、外键);
  疑似改名(类型相同、夹在同样两列之间、只有一对)只说「可能」;读不到唯一约束、外键的一边不比;升级后第一次读到
  (结构版本 0)只建基线,不把通用写法换成方言完整写法说成类型变了。
- 后台探查顺带记变化(data/introspect_jobs.py):刷新对象索引记新出现的表、真删除的表(第一次建索引只建基线);开始
  执行时记离开范围的有目录的表、范围规则和上一次不同;逐批读完拿上一次的结构比,读不到结构的记 table_reflect_failed。
  离开范围、读不到结构不算删除。记变化出错只记日志,不挡探查。
- 变化记录 source_changes(db/models_sources.py 的 SourceChange,格式 agentlab-change/1,seq 自增不复用、按指纹去重),
  记录与读取在 data/source_changes.py;取值观察的表 TableObservation 同文件建好。
- 影响面(data/catalog_impact.structure_impact):目录里说到被删、被改的列的项(列级项、业务主键、业务日期、有效记录条件
  按 parse_valid_filter 取列、两端的关联关系)带状态,人工确认过的升为要处理;已发布模板写死的 SQL 换上新结构再跑
  sql_unknown_columns,说得出「这条 SQL 现在会报列不存在」。sql_unknown_columns 从 api/copilot.py 挪到
  data/sqlcheck_columns.py 共用,copilot 那边只改 import。
- 发布时记 WorkflowVersion.schema_versions(只记直接引用、结构版本大于 0 的表);正式运行发起时比对,变了写 schema.drift
  (core/events.py 登记,只提醒、不拦);版本详情给出 schema_versions、schema_changes;发布前检查把这一版直接引用的表上
  还没处理的结构变化列成警告(schema.change_open),不拦发布。

测试:test_schema_diff(纯函数)、test_schema_changes(SQLite 夹具 ALTER 前后、影响面、去重、真删除与离开范围、
schema.drift、发布前检查、升级后基线)。
2026-10-16 16:26:11 +08:00
ThearchyHelios 86d2840e41 W3-d 邮件渠道、待办汇总、访问密钥到期提醒(后端)
邮件(04 细案 B2,集成-I4 / I5,改写应用平台 Q6):
- notify/mail.py:标准库 smtplib 放进 asyncio.to_thread,单次超时 10 秒;4xx 和连不上按 1/2/4 秒重试,5xx 不重试;
  报错只写主机名和服务器回话,口令一律抹掉;纯文本 + 转义过的 HTML,带 List-Unsubscribe 和 Auto-Submitted;测试可换假的
  SMTP 工厂。模块说明写清改写 Q6 的理由:SMTP 是可选配置、退信不追踪照实说「已交给邮件服务器」
- 通知渠道多一种格式「邮件」(core.CHANNEL_FORMATS;定时任务的 Webhook 只会发 HTTP,FORMATS 不加,免得定时任务能选
  邮件):个人邮件渠道不收地址,只发到名录里的账号邮箱,没有邮箱记一条失败投递并写明去「设置 → 身份」填;全局、应用的填
  固定地址,最多 20 个;邮件服务没启用时不收新的邮件渠道;渠道列表另给 mail(启没启用、本人的账号邮箱),投递记录带格式
- 设置的 mail 一组(只有管理员能改):口令用 core/crypto 加密,读的时候只说配没配;启用时要配全;发件人名称、主机不许
  换行;「发测试邮件」只发到管理员自己的账号邮箱,关登录、没有邮箱时 422 说清怎么办
- mail.default_for_assignees(缺省关):开了之后第一次派到待办的人自动有一个个人邮件渠道(订阅待办打开、动作结果未知、
  审核有结果),本人删了不再建回来;关登录时不建

待办汇总与密钥到期(04 细案 B3):
- notify/digest.py 接进调度循环的步骤列表:每天到 notify.digest_at(缺省 09:00,服务端时区)给订阅了 task.digest 的
  个人渠道发汇总(条数、最早时限、最多 5 条节点名、去待办箱的链接,没有待办不发);按日期在 settings 表里插一行认领,
  重启不重发;认领行和自动建邮件渠道的名单是通知自己记的账,设置接口不列不收
- key.expiring:到期前 7 天、1 天各推一次,个人密钥推本人的个人渠道、服务账号的推全局渠道,推之前记审计、按审计去重;
  轮换下来的旧密钥、撤销了的、建的时候就在这一档里的不推
- 两个事件推送实现了,才在 notify/service.EVENTS 打开它们的订阅范围

设置的 notify 一组加 digest_at;只给一部分时补上存着的另几项;环境变量锁住对外访问地址时只核对那一项,汇总时间照常能改。
apps 一组加 share_links(分享链接的开关,缺省关,下一个提交用)。测试:假 SMTP 工厂下的收件人、失败投递、重试、口令、
设置与权限、自动建渠道;汇总按时、只发一次、重启不重发;密钥两档、谁收、不该推的几种。
2026-10-16 16:21:41 +08:00
ThearchyHelios 1f8250699c 口径卡来源支持节点改名映射 caliber_from.nodes(口径账本做法 4 ③)
为什么:组合式问数、对账、召回要把一份口径的取数节点拷进另一张图,同一张图里放两份口径时节点 id 会撞,拷过来的取数节点得改名(c1_fetch)。以前 caliber_from 只认同名的取数节点,改了名口径卡就取不到输入。

做了什么:
- metrics._pinned_caliber 认 caliber_from.nodes {原 id: 新 id}:按语法树把式子里 nodes.<原 id>(含 cell(nodes.X, …) 的第一个参数、nodes['X'])的根换成新 id 再算,字符串字面量里的字不动;指标集的 source 照记映射。映射形状不对节点失败并说清哪一处。
- 画布校验 _caliber_from_issues:映射的形状和运行时同一个判断(rename_problem),改名后的节点要在图里、在口径卡上游。
- 升版检查照常挡、照常记事件,事件带上 caliber_nodes;证据的 _caliber_source / _caliber_upgrade 照带映射。
- 运行对比按映射把式子换回那一版里的写法再比:同一份口径换了节点名不算口径变了。推演按卡上记的改名后的式子原样重算。
- 没写 nodes 的老图一字不差:式子不经 unparse、来源只有三项、升版事件没有新键(单独测)。

测试:tests/test_caliber_from_nodes.py(改写规则、运行时取值、证据链、推演、校验、升版检查、对比、老图不变)。
2026-10-16 16:20:25 +08:00
ThearchyHelios 91cc150f17 下一轮第 3 波的契约骨架:工作流版本加表的结构版本;定时的非工作流目标按种类交给各自的模块(评测题集 app/eval/scheduled、数据源维护 app/data/source_maintenance,先放照实记「还不能发起」的占位)、record_fire 共用;分享链接、数据源变化记录、口径账本按答案出具与对账的路由先登记好权限 2026-10-16 15:51:33 +08:00
ThearchyHelios ee8c0317a2 合入 W2-d 访问密钥与服务账号:服务账号(管理员建、不能登录)与个人访问密钥(管理员开关,缺省关);只存哈希、必设到期、轮换与撤销;按路由白名单放行、封顶在使用者边界(看到的都遮过);发起入口和刷新视图必须带幂等键;视图数据接口 agentlab.viewdata/1;审计、请求记录、钩子收件记录;接口说明;设置里的访问密钥页 2026-10-16 15:38:33 +08:00
ThearchyHelios 9e687114f7 合入 W2-c 评测题集与期望答案:题集、题目、评测、结果的数据模型;三种期望来源(SQL、口径、从运行认定),每次以评测来源的探索运行重算,遮罩列拒收;口径升版的三种处置;全站唯一的派生图生成器 ledger/derive.py;目录快照与影子起草(不碰线上目录、不查业务库);题集导出导入;零售样例题集 2026-10-16 15:38:11 +08:00
ThearchyHelios 64ed51c95d 合入 W2-b 口径账本内核:sql_semantics 的参数绑定与等价证明(绑定只当启发,等价只由 explain_sql_diff 判,执行守门进语料);探索运行封存后在后台比对口径,结果存 caliber_match 工件并追加封存后事件 ledger.matched,启动补比;逐个数的口径标签与整题判断(GET /api/runs/{id}/ledger、证据片段、报告导出的「口径」一步);运行与问数据轮次、画布来源的封存链接;报告导出留审计;E1–E4 统计 2026-10-16 15:38:11 +08:00
ThearchyHelios 1a6082f033 大库的对象索引、探查范围和后台探查(next-round 03 A1),schema_view 预筛与按需加入、剖析事实的后端(A2、遗留 e 的底座)
为什么:几千张表的库以前用不了——探查每个源最多取 200 个对象、按数据库列出的顺序截断,同步请求、整份替换、不排队;
落在窗口外的表在目录、look、剖析里被说成「表已不在」。

做了什么:
- db/models_sources.py:source_objects(对象索引一行一个对象:范围、原因、结构指纹与 struct_version、first_seen / last_seen /
  removed_at、reflected_at、按需加入的留痕)、introspect_jobs / introspect_tasks(后台探查任务与逐表状态)。
- data/source_objects.py:每种方言一条字典查询建对象索引(失败退回 Inspector,上限 5 万,截断时不判删除);探查范围规则
  (options.introspect_scope:通配、按 name_key、排除优先、显式列出 / 移出;有目录的表、已发布模板写死 SQL 用到的表 pinned;
  命中超上限拒绝并说出命中数);唯一的 rebuild_schema_cache 从范围内已反射的行物化 schema_cache(形状不变,另加 scope 摘要);
  三种「不在」分开说(removed / missing / out_of_scope / reflect_failed);按需加入(当场只读反射、落盘、留痕、进程内按源加锁)。
- data/introspect_jobs.py:探查改成后台任务(core/job_runtime,lane kind introspect,和同源起草、剖析串行;按批反射、逐表记状态、
  可停止、读完的照样物化;重启标中断不续跑)。
- api/datasources.py:POST /introspect 返回 202 和任务,dry_run 同步只刷新索引、返回命中数;/introspect-jobs 读任务、停止;
  /introspect-scope 读、预览、保存规则;/schema?table= 带剖析事实、说清三种「不在」;DataSourceOut 加 schema_scope、introspect_job。
- schema_view:search_tables(BM25 倒排,遮罩列只用列名,范围外只按名字和注释、权重低一档)、profile_lines(剖析事实一节,
  附「只供写查询参考」,遮罩按渲染那一刻重滤,上传表格快照不一致时不给)、absent_detail。
- look:候选超过 PREFILTER_LIMIT 时只给预筛结果 + 已用到的表 + 一跳关联;点名或挑中范围外的表按需加入(每次查看最多 4 张),
  look 记录加 added_to_scope、prefiltered;表详情带剖析事实。
- tools/datasource:db_schema__ 加 keyword、不传 table 时只列前 50 张;点名范围外的表按需加入(每次运行最多 8 张);查询结果
  照实列「未按目录检查的表」(unchecked_tables,不新增检查规则);建工具时读剖析事实。
- 剖析:_relation_ends 分清「范围外,可以加入」和「表结构里没有」;任务记上传表格的 snapshot_id;last_facts。表助手、/schema
  用同一段剖析事实。
- 目录接口:清单只列范围内,absent_reason 分开说;GET /catalog/search 查对象索引,POST /catalog/scope/add 加入范围。
- 第 1 波留下的一处:check_valid_filter 和 stored_valid_filter 拼有效记录条件时按 quote_name 强制加引号。
- 工具参数纠错:空着的可选参数不止一个时,只认拼写明显接近的那一个(db_schema 多了 keyword 以后 tabel 仍能纠成 table)。
- 测试:3,000 张表的合成库(对象索引、缺省范围超上限拒绝、规则圈 400 张后台读完、停止、重启标中断、和剖析排队、同步起草
  409)、删除与结构版本、Inspector 回退、截断不判删除、范围规则、pinned、方言金样;改了同步探查的老测试改为等任务做完。
2026-10-16 14:17:06 +08:00
ThearchyHelios 545babc541 访问密钥收尾:服务账号进授权不进岗位的测试、关停时写请求记录的测试,几处整理
- 补测试:应用授权收服务账号、岗位绑定的检查把它算成错;改派给服务账号 422;调度循环停下时把攒着的请求记录写进库
  (去掉对应的代码回跑,测试抓得住)
- 门禁里只给拥有者的运行统一按 auth/access.PRIVATE_ORIGINS 认,不再各写一份字面量
- 幂等的 once 只把普通异常记成被拒:请求被取消时记录停在处理中,过了时限同一个键能重新认领(同钩子)
- 新建密钥的弹窗等服务账号取回来再说「还不能建」,不先闪一下
2026-10-16 14:15:51 +08:00
ThearchyHelios 0b825f40ee 设置里的「访问密钥」与钩子的收件记录(下一轮第 2 波 W2-d 前端,细案 04 A1、A6、A7)
- 设置 → 访问密钥:管理员看个人访问密钥的开关、服务账号(建、停用前确认)和全部密钥;别人只看自己的个人访问密钥,管理员
  打开开关之后才有这一页(认人接口多给一项 personal_keys:关着时建不了、已有的也不生效,给一页空的没有用)。新建时缺省只勾读
  的权限,服务账号勾不了回复待办,有效期必填最长 365 天;明文只显示这一次,点「我已保存」就没了;轮换、撤销先确认;关登录时
  照实说访问密钥不生效、不给建的入口
- 「最近的请求」做成共用组件(components/RecentRequests.tsx):每把密钥的请求记录、钩子的收件记录都用它,接口写路由模板,
  成功、被拒、处理中分开上色
- 接口说明:照服务端门禁那张表列出方法、路径、权限、说明、错误码,标出要幂等键的
- 应用编辑 → 钩子:生成过密钥的钩子能展开收件记录(状态、原因、发起的实例),写明近 1 小时验签失败几次
- 名录里的服务账号:选人控件缺省不收(岗位绑定、改派不能派给它),应用授权照收;身份页那一行标出服务账号,不给角色和密码的
  控件;审计按「访问密钥」筛
- 检查:新建 check-access-keys(不叫 check-tokens,那个名字查的是设计令牌),check-app-editor 的钩子一段加收件记录;登记进
  check-all
2026-10-16 14:11:30 +08:00
ThearchyHelios 7f14313416 评测题目的几处收紧:口径题只能固定已发布未退役的版本、处置升版后按新版本再核一遍、试算前结束读事务、影子起草分开记只写出一部分的表
- cases.resolve_case:口径题钉的版本没发布过或已退役的,保存时照实拒收(422,field=gold.version);以前要等试算跑到
  升版判断才说得出。
- 处置升版(recompute / incomparable)改钉新版本之前,按新版本再过一遍 resolve_case(入口字段、指标),说得清是哪一项不行。
- 接口里的试算要等一次运行跑完(最多两分钟):先结束这个请求的读事务再等,不一直开着它(导入时同样)。
- 影子起草:只写出一部分的表另记(tables_partial),快照照出、状态 partial;一张都没起草出来才算失败、不出快照。以前
  只写出一部分的表算成没起草出来,几张表都只写出一部分时整次记失败、写好的部分也丢了。
- 题集里推起草哪些表时,期望 SQL 按题集的时区渲染。
2026-10-16 14:05:22 +08:00
ThearchyHelios 5b4bd91476 口径账本内核:运行封存后在后台比对已发布口径、逐个数标口径、按运行读的账本接口、来源链和导出留痕、统计 E1–E4
为什么:问数据的答案要和已治理口径接起来(next-round 02 做法 2、3、9、10;总方案 X1、X2、X3、X12,拍板 L2、L4、L8)。

- 比对(ledger/match.py):探索运行(chat、canvas、trial、personal_app)成功结束、封存完、有查询快照时,在 runner 封存之后和
  notify_run 同一个位置放到后台;候选口径是每个工作流最新的已发布未退役版本里查库的调用工具节点(受管、已发布都算),按数据源
  和表预筛;逐个用 match_template 比,schema 用查询当时冻结的表结构、列名取同一份快照里冻结的数据目录。每次运行限时 8 秒、在
  线程里、同时最多 2 个;超时、快照被改过或读不出、SQL 解析不了照实记 skipped。启动时(run_manager.setup,和调度循环同一个开关)
  补比最近 7 天封存了却没比过的运行,只读工件和版本表。
- 记法(ledger/store.py,拍板 L8):内容寻址工件 kind=caliber_match(agentlab.caliber_match/1)+ run_append 的 sealed 方式追加
  ledger.matched(core/events.py 登记);工件里不放时间和工作流名,同一份封存再比一次哈希一样;meta 记 calibers / related / counts。
- 标签(ledger/labels.py):单元格按查询的比对状态;指标要每个输入都等价于同一份口径同一版、绑定一致,式子按「哪个取数节点、
  第几行、哪一列」换完和口径卡上的一字不差。caliber(how:independent 等价已证明;composed 助手选用留给第 3 波)/ related /
  reasoning / unknown;整题判断附还缺的入口字段。
- 读(ledger/view.py):GET /api/runs/{id}/ledger(ledger_api.runs_router,和证据同一道门),没比对过、能比的按需排上;证据
  片段接口加 ledger 键(没比对过的运行没有这个键,返回一字不差);报告导出的面板最上面多「口径」一步并写明「封存后比对,不在
  封存范围内」。遮罩按读的那一刻重滤:绑定的值是这次运行查到过的遮罩值时不给值,差异条目和逐行对照走 _Shown。
- 来源链:POST /api/runs 加 origin_ref(只在 origin=chat 时收,服务端核对对话访问权和轮次归属,写进 Run.origin 和封存的
  run.origin);WorkflowIn 加 origin(run_id、conversation_id、turn_id,核对规则同 from-run);报告导出和可验证报告下载各记一条
  审计 report.exported。可验证报告列封存后追加的事件时给 ledger.matched 一句只有条数的说明。
- 统计(ledger/stats.py):E1–E4 从工件、运行、查询快照、审计现算,GET /api/ledger/stats(搭建者);E3 用 app/chat/questions
  的聚类加按(数据源、normalize_sql 文本)的确定性重复;评测直接调 compute_stats(可点名 run_ids)。对账反馈、认证转化、按发布
  的召回在后面几波,照实写没有数据。

测试:test_ledger_match.py(真跑探索运行:等价、相关、没有相关口径、写死的码值、非探索运行、超时、快照被改、解析失败、预筛、
指标两种、级别、只认最新版、补比)、test_ledger_api.py(账本接口、按需比、遮罩、证据片段、导出、审计、统计)、
test_run_origin_ref.py(origin_ref 与画布来源的核对)。
2026-10-16 14:02:55 +08:00
ThearchyHelios a7e91b16a1 评测接口与题集导出导入、零售样例题集(第 2 波 W2-c)
- api/evals.py(骨架已登记、只给搭建者):题集、题目的增删改查(改要带读到的 version,对不上 409;有评测记录的题集不能删,
  只能归档)。保存题目时试算一次(细案 01 做法 8):没过只读守卫、用到遮罩列、取不到值、运行失败、超时、口径升版待处置的,
  照实回 422(带机器码和 field)、不保存;只改标签、启用的不重新试算。另有只试算不保存、试算已有的题。
- 从运行认定:GET /runs/{id}/queries 列出那次运行的查询(只给 SQL、列名、行数,不给格),POST …/cases/from-run 拷出一条
  查询的 SQL 建题,记确认人、时间、出自哪次运行;客户端不能自己写 from_run,要改 SQL 就改成 SQL 题。
- 口径:GET /calibers 和蓝图助手同一份口径清单(能不能复用、为什么);GET/POST …/upgrade 看、处置口径升版,
  recompute / dual / incomparable 三选一、没有缺省;不可比的旧题归档、按新版本另建新题。
- 目录快照:当前目录(当场写好)、影子起草的计划和估算、后台影子起草(202)、查看、停止。
- app/eval/transfer.py:题集导出成 JSON(数据源、工作流按名字;不含期望值、答案、运行,从运行认定只带确认人和时间),导入
  按名字对上、逐题过同一套校验和同一道试算关,对不上、没过的跳过并写原因,一道都导不进来就不建题集。
- tests/fixtures/eval/retail_suite.json:零售样例题集,配 tests/fixtures/acceptance/retail.py(数据源名 retail_demo,
  retail.SOURCE_NAME):支付日归属、退款、取消、华南高于华东,一道「华中区」期望没有数据,一道问法含糊期望反问。测试在
  按今天生成的零售库上导入,六道题都试算通过、期望和直接查库一样、陷阱按口径算。
2026-10-16 13:58:36 +08:00
ThearchyHelios 19329c873e 访问密钥与服务账号的后端(下一轮第 2 波 W2-d,细案 04 A1–A6、总方案 X8)
外部系统(门户、OA、BI)和个人脚本要用一个身份调接口,以前只能拿个人会话 cookie 去模拟。这次加访问密钥:

- 签发(A1):users.kind 区分人和服务账号(svc- 开头、没有口令、不能登录、角色固定使用者,管理员建);access_keys 只存
  sha256 和开头一截,明文 agl_ 加 32 字节随机串只显示一次;到期必填(最长 365 天、缺省 90 天)、可撤销、可轮换(新钥 + 旧钥
  24 小时后到期);个人访问密钥由管理员开关 auth.personal_keys 管,缺省关,关掉时已有的个人密钥一并不认
- 认人与门禁(A2):Principal 加 via / key_id / key_name / scopes / app_ids(全部带默认值);中间件认 Bearer agl_…,不看
  cookie、不查 CSRF、不看 X-Actor,认不出的各回 401 带各自的 code、绝不退回匿名;WebSocket 带密钥一律拒;关登录时 409
  keys_need_login。KEY_ROUTES(方法 + 路由模板 → scope)一张表,FastAPI(dependencies=[Depends(key_gate)]) 挂一次;不在表里的
  403 key_route_forbidden,缺 scope、限定应用、个人应用和试运行各有各的 403;每把密钥每分钟 120 次(429 + Retry-After)
- 封顶(A3):按密钥认出来的一律是使用者(拥有者现在的组),现有的使用者规则和遮罩全部自动生效,管理员的密钥也一样
- 写操作(A4):发起入口、刷新视图必须带 Idempotency-Key,api_receipts 按(拥有者, 键)去重,轮换后照样认得出;状态机从
  process/hooks.py 抽到 process/receipts.py,钩子和密钥共用(钩子现有测试原样通过),另按请求指纹拒掉同一个键办另一件事;
  有文件控件的入口 422;回复待办只给个人密钥、只限审批 / 补充输入 / 编辑稿件;run.origin、task.responded 记 via=key、哪把密钥
- 视图数据(A5):GET /api/apps/{id}/views/{view}/data(agentlab.viewdata/1),印章、清单哈希、口径卡指标原值和渲染文字,
  遮罩的 value 为 null(和页面同一个判断),按需视图只读缓存、不发起运行,只给正式运行
- 留痕(A6):建 / 撤销 / 轮换密钥、建 / 停用服务账号、开关个人密钥写审计;请求记录进程内缓冲、调度循环一拍里批量写、
  保留 90 天;last_used_at 至少隔 1 分钟写一次;钩子收件记录接口(manage),验签失败只在进程内按钩子计数
- 名录与派单:服务账号在名录里标出,岗位绑定、选人候选、提交审核、改派里排除,应用授权照收;服务账号发起的实例里派给发起人
  的待办交给任何人
- 接口说明(A7):GET /api/access-keys/reference 从 KEY_ROUTES 生成(要登录)

测试:签发与各种 401、门禁合约(遍历全站路由)、CSRF 与身份来源、封顶(管理员密钥取工件也是遮过的)、幂等(含轮换后、
半路杀进程)、留痕、待办种类、视图数据(真跑的遮罩会员库)、限流、钩子收件记录。
2026-10-16 13:48:09 +08:00
ThearchyHelios 898e95a3ef 下一轮第 2 波的契约骨架:用户加 kind(服务账号)、剖析任务加上传表格的快照 id、工件按种类的索引;新模型文件 models_sources、models_eval(整模块导入);评测、访问密钥的路由模块和口径账本按运行读的路由先登记好权限(评测只给搭建者,访问密钥登录即可,账本按运行读的和证据同一道门) 2026-10-16 13:07:40 +08:00
ThearchyHelios 3f5cf79b26 合入 W1-d 集成底座:安全响应头(frame-ancestors 缺省 self、白名单、script-src 先只上报);对外访问地址只留一个设置(环境变量优先);开登录时收起 /docs;集成接口的错误一律带机读码 2026-10-16 12:51:43 +08:00
ThearchyHelios 696cd17a4a 合入 W1-b 已知遗留:人工驳回记成已取消 + end_reason=rejected(不重试、不被出错时继续吞掉),各处照实写「被驳回」;个人渠道「我发起的实例完成 / 失败」生效;导入清单如实记「是否认证」;使用者选人按候选池、开登录时服务端核对;自由形蓝图按导入的图列「谁来做」;tick 按 id 断言 2026-10-16 12:51:43 +08:00
ThearchyHelios e96b0180d9 可验证报告、报告导出写「被驳回」:元信息带 end_reason,核对仍按运行取消
为什么:被驳回的运行导出后,可验证报告的运行结果写「已取消」,报告导出的运行状态也写「已取消」,和系统里「被驳回」两种
说法。封存里被驳回和手动停止是同一种结束事件(运行取消),分开它们的原因在那条事件的内容里,而运行取消的内容不随文件
附带(里面有驳回人和备注)——光看封存分不出来。

做了什么:
- 两种导出的元信息都带 end_reason(bundle/build、report_export/build、对应的接口)。
- verifier.js:运行结果按运行记录写「被驳回」,核对的仍是「最后一条结束类事件是运行取消」,旁边写「已核对运行取消,被驳回
  取自运行记录」,原因里写明这一半要去系统里对照;手动停止、老运行照旧「已取消」;文件里写被驳回、事件却是运行结束的照旧
  不通过。runtime.js 的运行状态照 end_reason 写「被驳回」。
- 样本页加一份 rejected(check-bundle 样本数 17 → 18),断言写被驳回、写明取自运行记录、不提重新封存。

测试:test_bundle_rejected 用 node 载入 verifier.js 核运行信息(被驳回、手动停止、老运行、伪造);test_bundle_build 核
元信息和样本页;test_bundle_api 核两种导出接口都带 end_reason。把实现退回去回跑,被驳回、伪造两条和元信息、样本页、
接口的断言都失败(手动停止、老运行两条守的是原样,照旧通过);改完通过。
2026-10-16 12:46:48 +08:00