Commit Graph

564 Commits

Author SHA1 Message Date
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 298e52bd69 按复核意见修后台探查的三处退化(A-1、A-2、A-3)
A-1 升级后第一次后台探查中途停下,表结构缩成已读完的那几批:
schema_cache 只从对象索引物化,老缓存(升级前同步探查写的,没有 scope 摘要)里的表结构却从没进过对象索引。
于是第一次探查只要停止、连接断开、服务重启,或者排队时有人按需加入、探查失败要记一笔,物化出来的就只剩已读完的表,
老缓存里还没重读的表静默消失,skipped 里也没有。先在探查范围面板刷新过对象索引、再探查却连不上时更糟:表结构清成空的。
现在这个 schema 的对象索引第一次建起来时(refresh_index 的 legacy,source_objects.legacy_seed),把老缓存里的表结构
接进对应的行:在范围里、算规则命中,带老结构和它的指纹,结构版本记 0(第一次真读到记 1,不把反射格式的差异当成
结构变化);老缓存里读不到结构的照样记成读不到。缓存的 schema 和配置对不上的不接,对象索引里没有的不接。第一次
任务开始时照规则重圈,没重读到的沿用老结构,和以后每次探查中途停下一样。

A-2 schema 填错、一个对象都没有时不再给候选 schema:
PostgreSQL、Oracle 的 schema 写错时字典查询只回 0 行,不抛错,新路径就只物化出 tables={} 并盖上同步时间,
available_schemas 没了:卡片上「该服务器上还有……」、助手摘要里「数据可能在这些 schema」都给不出。现在 prepare
拿到 0 个对象时照旧用 list_schemas 拿候选,记在任务的 scope 里,物化时由 rebuild_schema_cache 写进 available_schemas
(不置 failed:这不是一次失败);有了对象再探查就不再挂着。

A-3 排队中「加入范围」的表在任务开始时被移出:
_apply 只保留建任务时圈好的表和所有 on_demand 的行,目录页显式加入(explicit)的表被当成离开范围、清掉结构。
现在保留 added_at 不早于建任务时刻的行(按需加入、显式加入都算),原因按 pinned > 显式 > 规则 > 按需取高的;
建任务之前就按需加入、又被显式移出的不再一律留着,照规则移出(以前「留着所有 on_demand」会让显式移出不生效)。

测试(tests/test_introspect_jobs.py):60 张表的老缓存,第一批读完后停止 / 第二批连接出错 / 重启标中断 / dry_run
后探查失败,表数都不减少,再探查一次结构版本全是 1;别的 schema 的老缓存不接;空库和替身模拟的 0 对象 schema 带上
候选、DataSourceOut 和助手摘要都看得到;排队中显式加入、按需加入的表任务跑完仍在范围里。去掉三处修复各自回跑,
对应的测试都失败。
2026-10-16 15:36:21 +08:00
ThearchyHelios 9cd45ca047 评测题目返回给界面的说明改成合规文案:check-copy 三项规则恢复全过
check-copy 也扫后端交给界面的报错文案,nr2/c 交付时没跑这一道,有三项没过(复核 C-1):

- 口语补语:期望 SQL 行数不够时写的「取不到值」「取不到第 N 行」,改成「没有可用的值」「没有第 N 行」;
  同一处 where 匹配到多行的「取不准是哪一行」一并改成「无法确定是哪一行」。
- 禁用词「钉住」「改钉」:升版处置的叫法(UPGRADE_LABEL)和 DisposeRefused、升版状态的说明,按全站
  「固定版本」的说法改成「固定的…」「改为固定新版本」,和应用、子工作流的升版说明一致。
- 半角标点和英文引号:题目 where 写错时的报错例子原来是一段 JSON,改成全角的「地区:华南」,并写明取值只能是
  文字或数字(校验本来就只收这两种)。

只改文案,判断和机读码都不变。代码注释和模块说明里的「钉」不上界面,照全仓库的写法保留。
2026-10-16 15:23:44 +08:00
ThearchyHelios f9f5338ec6 数据源不可用时的 db_schema 替身也收 keyword;重读范围内读不到结构的表不记成按需加入;起草、剖析排在探查后面的测试
- tools/datasource._unavailable_tool:db_schema__ 多了可选的 keyword,固定的版本不在了时的替身也要收这个参数(调用工具节点按
  参数模型的全部字段传参,不收就报「不接受名为 keyword 的参数」,全量测试里 test_run_data_versions 抓到的)。
- source_objects.add_to_scope:本来就在范围里、只是上次没读到结构的表(pinned、规则命中的)这次重读一遍,不改原因、不记留痕,
  不算进「每次运行最多按需加入 8 张」,也不出现在面板的「按需加入的表」里。
- tests/test_introspect_jobs.py:起草、剖析任务排在同源的探查任务后面时 waiting_for 是 introspect。
2026-10-16 14:53:41 +08:00
ThearchyHelios 64ca6cb75b look:交回给模型点名的预筛目录只对这一次查看的需求有效,每次查看开始时清掉
为什么:挑选模型拿不到时(没有可用的模型)pick 不会重建预筛目录,index() 会交回上一次查看、另一个需求的那份。
2026-10-16 14:50:26 +08:00
ThearchyHelios f4755056e6 前端跟上后台探查:数据源卡片上的进度、停止、结果,探查范围面板;目录清单的几种「不在」、分批显示、搜索范围外的表并加入范围
为什么:探查改成了后台任务(POST /introspect 回 202 和任务),界面不能再等同步结果;几千张表的库要能设范围、看命中数,
目录清单要说清表为什么不在,范围外的表要找得到、加得进来。这一波直接进 main,不能有半截的功能。

- 数据源卡片:「探查结构」开始后台任务,进行中每秒取一次进度(进度条、正在读哪张、排在谁后面),「停止」停在当前一批之后;
  结束时换上任务带回的数据源、说一句结果;最近一次没做完(部分读不到、停止、中断、失败)常驻一行,读不到的表可展开。
  规则命中超过上限时照实说命中数并给「调整探查范围」;已有任务时跟上那一个;对象清单拿不到时重取卡片显示原因。
  有范围外的表时写「探查范围 400 / 3,000」。换 schema 后的重新探查、按配置重新探查、新建后的探查都走任务。
- 探查范围面板(IntrospectScopeDialog):全部对象 / 按规则,包含、排除(排除优先)、显式列出、显式移出、包含视图、上限;
  边改边数命中(停手 400 毫秒问服务端,不连库),超上限写明会被拒绝、「保存并探查」点不了;一律在范围内的表说明和举例;
  按需加入的留痕;没有对象索引时「刷新对象索引」(dry_run)。界面不算命中,规则只在服务端一处。
- 数据目录:表清单一次画 200 行、「显示更多」;表结构里没有的按 absent_reason 分开说(只有真删除说「表已不在」),老服务端
  不给原因时照旧;有范围外的表时搜索词同时在对象索引里找,命中列在清单下面、「加入范围」后清单重取;顶部说明按探查范围写
  (老缓存照旧说只探查了前 N 张、请重新探查);表详情按原因说;起草、剖析排在探查后面时写明。
- 运行面板的查看记录读 added_to_scope,展开区写「按需加入探查范围」。
- 类型、接口、文案各写各的块(types.ts / typesApp.ts 末尾、client.ts 的 datasources 和 dataCatalog、terms.ts 的
  INTROSPECT_TEXT / INTROSPECT_SCOPE_TEXT / CATALOG_ABSENT_TEXT / CATALOG_SCOPE_TEXT),SCHEMA_PARTIAL_TEXT 改成按探查范围说。
- 检查:check-manage 的假探查改回 202 和任务、加「数据 · 后台探查与探查范围」一节(超上限、刷新对象索引、边改边数、
  保存并探查、进度、停止、结果);check-catalog 的「只探查了一部分表」改成老缓存的说法,加「探查范围:三种不在、分批显示、
  加入范围」一节。两项检查 1,135 项全过。
- introspect_jobs 的一句报错不用「连不上」(check-copy)。
2026-10-16 14:46:58 +08:00
ThearchyHelios 4ba4a21b2c 大库的 look 预筛与按需加入、剖析事实四处一致、有效记录条件按 quote_name 加引号的测试;对象索引批量更新改走按表的 executemany
为什么:A2 和遗留 e 的行为要有脚本化的验收,且能证明测试抓得住回归(去掉遮罩重滤、去掉 quote_name 强制引号后对应用例都失败,
已回跑确认)。

- tests/test_look_large.py:3,000 张表的合成库,探查范围 300 张 + 5 张有目录的表,其余只在对象索引里(注释写进索引,模拟
  MySQL / PG 的表注释)。10 条脚本化需求挑中 ≥ 9 条、挑选提示词 < 20,000 字(挑选用按字面重合打分的假模型,只看得到交给它的
  那份目录);挑中范围外的表按需加入、记 added_to_scope 和留痕;点名的每次查看最多 4 张、超了照实说;db_schema__ 的 keyword、
  只列前 50 张、每次运行最多 8 张;查询照实列「未按目录检查的表」;目录搜索和「加入范围」;剖析的关系指向范围外时不说
  「表结构里没有」。
- tests/test_profile_facts.py:look、运行时 db_schema、/schema、表助手四处同一段剖析事实和固定提示;剖析之后加遮罩,那一列
  的范围不出现;没有剖析结果时表详情一字不差;上传表格快照不一致(含没记快照的)不给;剖析任务开始一张表时记下快照;
  「上次剖析」取真正开始过的那一次。
- tests/test_valid_filter_quote.py:存进目录的写法和剖析核对查询都按 quote_name 给 Oracle 小写列加引号。
- source_objects.refresh_index:注释、类型、行数估算有变的行改用按表的 executemany(ORM 的按主键批量更新要求参数里写主键
  列名,3,000 张表的库第二次刷新索引时报错)。
- db_schema__ 不传 table 时说清没列出的里有几张在探查范围外。
2026-10-16 14:26:04 +08:00
ThearchyHelios 0a3c45eb97 口径账本:合并查询的格照实写为什么没比对;账本测试按用例各用一个数据源隔开
- ledger/match.py:合并查询的快照不和口径比(它的数来自各个输入的查询),引用了它的格以前标成「没有比对记录」,现在写明
  「合并查询的格不和口径比对」;它不进工件的 queries,统计不数它。
- 测试:账本的用例以前在开头把别的用例发布的版本一律退役,好让候选口径只剩自己的——并行时会碰到同一个进程里别的测试模块的
  数据。改成每个用例一个名字不同的数据源(候选口径本来就按数据源预筛),不动别人的数据。补启动补比随调度开关起后台任务的用例。
2026-10-16 14:23:41 +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 7576fa6ce9 前端跟上口径账本内核:时间线认得 ledger.matched;问数据的运行带上属于哪段对话、哪一轮;「在画布里打开」带来源
为什么:这一波合入就进 main,后端新加的封存后事件、来源链要在界面上接得住,不能有订阅了收不到、点了没用的地方
(next-round 02 做法 2、9)。账本的标签、证据面板的「口径」一步、对账在第 4 波接界面,这一波只有接口。

- run/decode.ts:ledger.matched 解读成时间线上一行「比对已发布口径:1 条查询等价已证明,2 条有相关口径」,副标题写明「封存后比对,
  不在封存范围内」,事后补比、按需比的另写「按现在的口径回看」;有没比完的标成提醒;不改相位。文案进 lib/terms.ts 的 LEDGER_TEXT
  (连同 X10 的叫法:来自口径 / 相关口径 / 这次的推理 / 没有比对,等价已证明 / 助手选用,受管 / 已发布)。
- store/chat.ts:发起运行时带 origin_ref {conversation_id, turn_id}(等开轮请求拿到服务端的轮次 id);服务端核对不过
  (origin_ref_invalid:对话刚被删)时不带来源照常发起——来源只是留痕,不挡这一轮的回答。
- pages/ChatPage.tsx:「在画布里打开」带 origin {run_id, conversation_id, turn_id}(这一轮跑过才有);核对不过时不带来源照常新建。
  run/AssistantStream.tsx 的 onOpenGraph 多给一个轮次 id(只加参数,别处不受影响)。
- api/client.ts:runs.start 的 origin_ref、workflows.create 的 origin。
- 检查脚本:check-decode 加「口径账本」一节;check-chat 断言发起时带上 origin_ref、「在画布里打开」带上来源;check-report-export
  加「口径一步」:没比对过的运行没有这一步,数据块里放一份编的标签断网打开,面板最上面是「口径」、写明封存后比对、遮罩的参数写
  「已遮罩」(只打印这一节,不打印真数据)。

验证:tsc、check-copy;check-all --stacks 1 decode chat report-export 全过(1485 项)。
2026-10-16 14:15:22 +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 730d3a8ff4 评测期望的遮罩拒收测试:每种写法的期望直接写在参数里,不再用嵌套的条件表达式拼 2026-10-16 14:06:29 +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 2b9dcc55e3 评测题集与期望答案(第 2 波 W2-c)第一步:模型、派生图、期望的三种来源、目录快照与影子起草
- db/models_eval.py:EvalSuite、EvalCase、EvalRun、EvalResult 一次定全(第 3 波的执行、评分直接用),另加
  EvalCatalogSnapshot 登记目录快照(谁、何时、来源、影子起草的进度和用量)。EvalResult.job_id 是 eval_run_id 的
  别名:后台任务运行时的收尾按 task_model.job_id 认逐项。预算缺省按评测-D9(同时 2 题、每题 900 秒、单次 2 美元、
  每日 5 美元),这一波只定字段。
- app/ledger/derive.py:全站唯一的「从钉住版本派生只读图」的生成器(总方案 X3)。这一波实现口径探针:拷出口径卡读
  的取数节点(id、配置、图级 defaults 原样)+ caliber_from 固定到那一版的口径卡;只认可复用的源头卡,取数节点只许
  引用入口字段,别的照实拒收。对账(这次的取数 + 口径 + 对账卡)、召回(新旧两版带前缀、caliber_from.nodes 改名
  映射)的扩展点写在模块说明里,assemble / caliber_block 直接可用。
- app/eval/cases.py、suites.py:题目和题集的形状与校验。数值期望不许人填(评测-D4,写了 value 一类的键一律拒收),
  文字要点单列、不计入准确率;只做单轮(评测-D12,history 必须为空);变量只认定时任务那份 {{ schedule.* }};
  题目指纹去掉确认人、出处运行、升版处置这些记录项。
- app/eval/gold.py:SQL 题、口径题、从运行认定三种期望。每次都以来源 eval、只读 + 遮罩的探索运行重算,期望值取封存的
  查询快照(或口径卡的指标集);只读守卫拒写语句、用到遮罩列的拒收(认法同证据面板的派生遮罩)、取不到值 / 失败 /
  超时 / 停在人工确认上各写各的原因。口径升版用和正式运行同一个 unresolved_caliber_upgrades 判、退役按
  subgraph_pin_problems 判,待处置就跳过;recompute / dual / incomparable 三选一、没有缺省。作答前后各算一次
  (bracket / settle)给第 3 波的执行调。
- app/eval/snapshots.py:目录快照存成 kind=eval_catalog_snapshot 的内容寻址工件(读法即 data/catalog_view.py 定的
  那一种);影子起草是不占数据源队的后台任务,起草写进内存里的目录副本,不调 write_entry、不碰线上目录、不查业务库,
  只起草题集用得到的表,开始前估算花费;停止、出错的不出快照。
- data/catalog_draft.py 只加入口 run_shadow_draft(同一个起草引擎,唯一不同是不去业务库数有效行过滤的排除比例);
  data/catalog.py 把写库前的并入抽成纯函数 merge_draft、建起草模型抽成 build_draft_model(行为不变,影子起草和写库
  用同一套并入规矩、同一个模型建法)。
2026-10-16 13:49:53 +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 68ea3a7f47 口径账本内核第一步:sql_semantics 加参数绑定与等价证明(bind_template / match_template)
为什么:口径账本要认出「这次执行的 SQL 是不是某一版口径的取数」。只比 normalize_sql 的文本会错给信任(status = 'paid'
和 'refunded' 文本相同),拿模板原文直接比 explain_sql_diff 又永远不等价(参数不同)。做法照 next-round 02 做法 1:

- 绑定只是启发:模板里的 {{ input.<字段> }}(可带 trim / compact)按位置换成哨兵,依次在 optimizer、light、parsed 三级
  规范形式上抹掉字面量的值比结构,结构一样就按位置配字面量(半截字面量按正则取,写死的常量必须相等,同一槽位取同一个值);
  三级都配不上时退回「同一列、同一运算符」的启发式配对(只给说明和预填)。
- 等价只由按绑定 render_template(运行时同一个渲染器)渲染出的口径 SQL 交给 explain_sql_diff 判;有槽位既没取到值又没有
  缺省值时不证明(渲染出 EXISTS (SELECT  FROM …) 这种口径实际不会执行的 SQL,sqlglot 照样能解析)。
- 不可比照实给原因:引用 vars. / nodes. / file_text、参数写在名字里、别的过滤器。
- 等价时按位置给列映射(外层别名不计、列先后计,SELECT * 按表结构展开)。

测试:新增 test_sql_binding.py(认得出、不错给信任、不可比、列映射);test_sql_equivalence.py 加「口径模板」一节,语料里
每一对把 B 的头两个字面量换成槽位当模板拿 A 比,判了等价的在虚构库上执行结果必须一致、explain_sql_diff 也必须判等价。
去掉「槽位没值不证明」那一道回跑,这一节抓到 11 对执行报错。
2026-10-16 13:39:15 +08:00
ThearchyHelios 301a599e9c 骨架补上口径账本的包 app/ledger(账本内核和评测的派生图都要往里写) 2026-10-16 13:09:07 +08:00
ThearchyHelios 898e95a3ef 下一轮第 2 波的契约骨架:用户加 kind(服务账号)、剖析任务加上传表格的快照 id、工件按种类的索引;新模型文件 models_sources、models_eval(整模块导入);评测、访问密钥的路由模块和口径账本按运行读的路由先登记好权限(评测只给搭建者,访问密钥登录即可,账本按运行读的和证据同一道门) 2026-10-16 13:07:40 +08:00
ThearchyHelios 736b031282 SQL 检查认时间列时带上 ENUM 的取值列表:PG 里类型名带 DATE 的枚举列,剖析不认是日期列、SQL 检查却认,两边对不上 2026-10-16 12:53:07 +08:00
ThearchyHelios 3f5cf79b26 合入 W1-d 集成底座:安全响应头(frame-ancestors 缺省 self、白名单、script-src 先只上报);对外访问地址只留一个设置(环境变量优先);开登录时收起 /docs;集成接口的错误一律带机读码 2026-10-16 12:51:43 +08:00
ThearchyHelios 02abede047 合入 W1-c 目录方言与 Excel 期 5:剖析方言修正(IN 两边类型族、列类型存方言完整写法与 ENUM 码值、Oracle 小写引号名、统计过期复核、无效日期、按存储顺序取前 N 行的说法)、方言金样与 sqlglot 校验、本机容器集成脚本;Excel 列表按期累积、穿插小计、表格对象起草、CSV 走配方 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
ThearchyHelios b6cb5816db 问数据:被驳回的那次尝试不写「已手动停止」,会话列表标「被驳回」
为什么:一轮的运行被审批人驳回之后点「重新运行本轮」,「之前的尝试」那一行写「已手动停止」,用户其实没有停过它。会话
列表的 last_status 把被驳回的一轮归成 cancelled,左栏不标——在等审批的那一问被驳回之后,看起来像是答完了(以前驳回
记成失败,左栏标的是「失败」)。

做了什么:
- store/chat 的 attemptOf:被驳回的那次记 outcome=rejected,摘要写「被驳回:张工 驳回了「查数」:…」;老留档里记成
  cancelled 的不改写。ChatPage 的尝试徽标认 rejected。
- api/conversations 的 last_status:前端落库时 meta 里记着 rejected 的、页面关掉没落库而运行是 cancelled +
  end_reason=rejected 的,都记 rejected;手动停止照旧 cancelled。types 的 last_status 加上 rejected。
- 左栏:打开过的看最后一轮(相位 cancelled 且带着驳回),没打开过的看 last_status,都标「被驳回」(不画异常色)。

测试:test_conversation_status 加一条(两种认法的被驳回、两种手动停止),改之前失败;check-chat 加一节被驳回(刷新后
认得出、补救区的话、左栏两种、重新运行之后的尝试记录),把前端改动退回去回跑,那一节 4 项不通过,改完通过。
2026-10-16 12:46:36 +08:00
ThearchyHelios 5c34ac6bc5 驳回不重试、不被「出错时继续」吞掉:人的决定照总方案 X9 收成被驳回
为什么:NodeRejected 是 NodeError 的子类,compiler._execute 的通用 except 照常接住它。节点配了失败重试的,驳回之后
会重试这个节点——再开一次待办、把同一件事再问一遍人;配了 on_error=continue 的,驳回被当成节点失败吞掉,图接着往下
走,最后以 succeeded 结束,通知、定时推送、发布单都看不到这次驳回。这和 X9 定下的「驳回 = cancelled + end_reason=
rejected」直接矛盾。「失败重试」「出错时继续」是给故障准备的,驳回不是故障。

做了什么:
- _execute 在通用 except 之前单独接住 NodeRejected:不重试,直接跳出;容错分支多一条「不是驳回」才走。节点这一步
  照旧记一条 node.failed(实例进度、时间线、存量判断都按它收尾这一步),再原样交给 runner 收成被驳回。
- NodeRejected 的说明改成现在的样子(以前写的是 on_error=continue 照旧认它)。

原有行为变了:配了重试或出错时继续的关卡,驳回后不再重问、不再接着走。发布说明里要写明。

测试:test_run_rejected 加五种关卡 ×(出错时继续、失败重试)的参数化,断言终态是 cancelled + rejected、没有重试日志、
驳回之后没往下走、节点只有一条 node.failed。先跑:出错时继续的五条以 succeeded 结束,失败重试的五条又停回 interrupted
(重问了人),10 条失败;改完通过。
2026-10-16 12:46:35 +08:00
ThearchyHelios 80fcf27732 配方导入只把 .csv / .tsv 当 CSV 读;网页、XML 冒充的 Excel 照旧拒收并说清楚
复核发现(C-csv-sniff-any-text):配方导入的扫描入口 scan_any 只看内容,不是 zip、不是 OLE、前 64KB 没有 NUL
就当 CSV 读,不看扩展名。企业系统常见的「HTML 表格冒充 .xls」、误传的文本文件,会被拆成一张叫「CSV」的工作表去
起草、核对;给已有 xlsx 配方传新一期时还会报找不到工作表、给出 rename_sheet 修复,都误导用户。

改法:
- 和简单导入(tabular._CSV)一样按扩展名定:scan_any(原件, 文件名) 只把 .csv / .tsv(且内容是文字)按 CSV 读,
  别的照旧交给 xlsx_scan 拒收。内容以「<」开头的(网页、Excel 2003 的 XML 表格,含带 BOM 的 UTF-8 / UTF-16)另给
  一句「它其实是网页或 XML 格式……请用 Excel 打开后另存为 .xlsx 或 .csv」。扩展名是 CSV、内容却不是文字的(图片
  改了扩展名)照实说;内容其实是 xlsx 的照工作簿读。
- .tsv 一律按制表符分隔(同 tabular):每行逗号、制表符个数都一样时,csv.Sniffer 优先挑逗号,会把 TSV 拆错。
- 扫描时定下的分隔符记进工作表的 path("csv:," / "csv:\t"),读格子(xlsx_cells.sheet_rows)用同一个,重放、
  试运行不再各嗅各的;这一版之前只有「csv:」的照旧再嗅一次。
- Pipeline.scan 改成 (原件, 文件名),recipe_imports 的四处调用都传上暂存区记下的文件名。

测试:test_csv_grid 加 .txt 的 CSV 内容拒收、四种网页 / XML 冒充 .xls 的拒收说法、.csv 扩展名的图片、TSV 按制表符
并且分隔符经扫描 JSON 往返后读格子照用、老暂存区的 path;改回只看内容回跑两条失败。
2026-10-16 12:23:19 +08:00
ThearchyHelios 70c5a006d2 像小计的行只认夹在明细中间的;可以在配方里声明「没有小计行」,不再每期确认
复核发现(C-subtotal-suspect-false-positive):没配小计时,_suspect_word 对只有「标签文字列 + 数字列」的列表
不加限制,「其余文字列为空」自然成立,首列以 合计/总计/小计/汇总/累计 开头的普通数据行都被报 subtotal_suspect,
最后一行也报(说明却写「夹在明细中间」)。这是必勾的确认项:存量配方(「累计充值」「累计消费」这类键值清单)
升级后每一期导入都多一项,工作流里「按配方导入一期」直接被 files/imports._needs_people 挡下;确认又记不住,
只能用 declare_subtotals 把真数据改成小计。

改法:
- 执行器(recipe_engine._watch_suspect / _settle_suspects):这一行本身像小计(首列以合计字样开头、其余文字和
  日期列空着、有数)之外,还要上一行明细在某个文字或日期列上有值、这一行那一列空着,并且下一行明细在同一列上
  也有值(或者下面紧跟着合计行——最后一组的小计也在列表里面)。候选先挂着,等下一行明细再定;列表到头了还挂着的
  不报。两列的键值清单因此不会报;上下明细那一列本来就空着的也不报。
- 配方新增 ListRows.no_subtotals(缺省 false,规范化时省略,存量配方的哈希不变):人确认过这张列表没有小计行,
  以合计字样开头的行照常是数据,执行器不再报 subtotal_suspect。它是放宽,出确认项 no_subtotals:<块>,签名只看
  这个开关,配上(或改过)的那一次确认一次,之后各期不再出。和 subtotals 二选一,同时配了报 subtotals_conflict。
- 配方面板「小计行」里没勾「有小计行」时多一个勾选框「没有小计行(以合计字样开头的行也是数据,不再提醒)」;
  勾上「有小计行」时自动去掉它。subtotal_suspect 的说明改成「上下明细都有值的文字列在这一行空着」,并说明以后
  不想再确认去哪里勾。
- check-manage 加一步:勾选「没有小计行」发 PUT,no_subtotals 为 true。

测试:test_recipe_p5 加复核的两列反例(门票收入 / 累计收入 / 汇总调整 / 餐饮收入;最后一行「合计调整项」)、
最后一行和上下明细那一列空着的反例、夹在中间和紧跟合计行的正例、no_subtotals 的导入、确认项、签名和冲突校验;
去掉「要有下文」的条件回跑两条失败。
2026-10-16 12:17:33 +08:00
ThearchyHelios 479cec0af7 通知事件一次登记(总方案 X6、5.1):六个新事件进 notify/service.EVENTS,推给哪几条路也登记在那一处
复核指出 X6 漏做:第 2、3 波的数据源变化、口径召回、评测、触达四块都要往 EVENTS、EVENT_LABEL 和前端设置页的事件列表里
加东西,正是 X6 要避免的同一处多方冲突;消息里能放什么也没有一处先定好。

做了什么:
- EVENTS 改成登记表:每个事件一行,叫法 + 推给哪几条路(scopes:global 全局渠道自己订阅、user 相关的人自己的个人渠道、
  app 应用版本 spec.notify 的订阅)。本轮新登记 source.changed、ledger.recall、eval.regressed、eval.failed、task.digest、
  key.expiring,scopes 都是空的——登记了、还没有推送。实现推送的那一块只改自己那一行的 scopes(各行之间隔着说明行,几块并行
  改也不会挨着冲突),前端叫法表不用再动。EVENT_LABEL 从登记表派生,老的 import 不变。
- 模块说明写清消息结构:标题、状态、条数、链接,加编号和搭建时起的名字;不放报告和答案里的数、表名、列名、取值、SQL、
  按运行数据渲染出来的句子。
- _channels 只走事件登记了的路;没登记的事件直接报错(推送之前先登记)。现有五个事件登记的路照现在的做法,行为不变。
- 接口:订阅按登记核对(subscribe_problem):没推送的「还没有推送,暂时不能订阅」,范围不对的「不推给范围是「应用」的
  渠道」。只核对这次新勾的:以前存下的照旧,开关、改名不会因此 422。GET /api/notify/events 多给 scopes(空的照实给)。
- 前端:terms.ts 的 eventLabel 和 EVENTS 一一对应、逐字一致,六个新事件先有叫法;设置页的渠道编辑框、应用的通知改按
  GET /api/notify/events 列(这种范围收得到的才列,已经勾着的照样列出来能取消;取不到时照实说)。

顺带修掉的(就在要改的那张列表上):设置页以前按前端叫法表的键列事件,里面有五个服务端从来不认的(待办升级、待办超时、
待办处理完、动作失败、动作已生效),一勾保存就 422;改派、升级、催办只在投递记录里出现,另起 deliveryEventLabel(加上测试
消息),记录里照实写是哪一种。服务端 EVENT_LABEL 的四句老叫法(有待办派给我、实例完成、实例失败、写入结果未知)改成和界面
同一句,界面上看到的字不变。

验证:新增 test_notify_events(登记表、前后端逐字对照、接口拒收、只走登记了的路),去掉 _channels 的按路过滤或「只核对新勾
的」回跑各有一项失败;check-capabilities 加一段「可订阅的事件照服务端登记列」,把列表改回按叫法表的键列,四项失败。
2026-10-16 12:12:14 +08:00
ThearchyHelios 53d510357a ENUM / SET 只认 MySQL 带括号的写法;PostgreSQL 的域记底下的类型
复核发现(C-enum-prefix-match):类型写法改成按方言编译以后,PostgreSQL 的枚举、域编译出来就是类型名本身,
而 _type_family、is_date_column、_is_enum 和码值检查里的 is_set 都按前缀 startswith(("ENUM", "SET")) 认:
- 叫 setup_status 的 PG 枚举被当成 MySQL 的 SET,列定义里这次没出现的取值不写进码值;
- 叫 settlement_amount 的数值域被认成文字,又被当成码值候选。

改法:
- catalog._MYSQL_ENUM 只认 ENUM( / SET((_mysql_enum_kind),四处都改用它;PG 的枚举一律靠探查记下的
  enum_values 认(column_family、_is_enum 本来就看它)。
- is_date_column 加 enum_values 参数:有取值列表的不算日期列。PG 枚举的类型名里碰巧带 DATE(update_reason)
  以前会被认成日期列;剖析这边(catalog_profile._is_date_column)已经传上。sqlcheck 里那一处归 W1-a,没动,
  不传时行为和原来一样。
- 探查(introspect.type_text、_enum_values)遇到 PG 的域(DOMAIN)记它底下的类型(域套域一路往下找):域名
  看不出存的是数字还是文字,名字里的词还会让类型大类、日期列、码值候选认错,模型照着写 SQL 也要知道底下是
  NUMERIC(12, 2) 还是 DATE。套在域里的枚举照样记下取值列表。升级前存的是 str(类型) 即「DOMAIN」,同样认不出。

测试:test_profile_dialect_units 加以 set、enum 开头的 PG 类型名、带取值列表的 PG 枚举码值、DOMAIN 编译成底下
类型的单测;改动前 8 条失败。
2026-10-16 12:06:24 +08:00
ThearchyHelios ad1092900c 剖析的关系覆盖率把对不上类型的键算作没对上;升级前旧说法的剖析备注不再让目录升版本
复核发现两处(C-relation-coverage-inflated、C-profile-note-rewording-bumps-catalog):

一、覆盖率虚高。key_literals 把父列是数字、子列取到的不是整数写法的文字('WALKIN'、'GUEST0')从覆盖率的
分母里拿掉了:散客代号占九成的一列只剩 1 个整数写法的键对上,就测成覆盖率 100%、升为「已核实」,模型的
db_schema、自查、冻结快照都会照这条错的关系写 join。基线上同一份数据是 proposed、覆盖率 3.1%。
- 整数主键里永远没有这些文字,它们照样是子表的键值,算作没对上、留在分母里:
  覆盖率 = 对上的 / (拼进 IN 的 + 按类型就对不上的)。全都不是整数写法时不发父表的核对查询,覆盖率 0%、
  「可能不是这条关系」,不再整条判「无法核对」。
- key_literals 改回 KeyLiterals(literals, unmatchable, unsafe):不能安全拼进 SQL 的值单独计数,不再用
  「取到的 − 拼进的 − 不同类型的」倒推('0012' 和 '12' 写成同一个字面量时会被错说成「类型不支持核对」)。
- 被指向列是日期时间的关系整条跳过,并且在发子表取值查询之前就跳过、不占这张表的查询预算;说明改成
  「各库的时间写法和精度不同,不核对这种关系的覆盖率」——子表一侧也是时间时,以前写的「两端类型不同」不照实。
- 备注里「另有 N 个键值和被指向列的类型不同,未计入」改成「其中 N 个不是整数写法,被指向列是数字,算作没对上」。
- 报告里覆盖率不足的那句「抽样覆盖率 N,保持推断」也改成「覆盖率 N」,和备注一种说法。

二、升级后第一次重新剖析,数据没变也给每张有剖析结论的表升一个版本。剖析备注改了四处说法(抽样覆盖率→
覆盖率、子表抽样→子表取了、(只看前→(按存储顺序只看前、按前 N 行统计→按存储顺序取前 N 行统计),
catalog.same_profile_note 没认成同一句,已发布工作流记下的目录版本对不上,之后每次正式运行都报
catalog.drift。
- same_profile_note 比较前先把这四处固定句式的旧写法换成现在的写法(_LEGACY_PROFILE_WORDING,句式带着前后文
  匹配,不会误换人写的原话);算相同时保留目录里的旧备注,旧说法等结论真变了、重写备注时才换掉。
- 起草时核对有效行过滤写在模型依据后面的那句「剖析核对:按前 N 行统计」同样只认那一句的旧写法。
- MySQL / MariaDB 上新加的排序规则说明、ENUM 列定义里没出现的取值、无效日期个数是新信息,这些备注照常
  升一次版本,要写进发版说明。

测试:test_catalog_profile 加文字键指向整数主键的端到端用例(去掉修复回跑:测成 verified、覆盖率 100%);
test_profile_dialect_units 加覆盖率分母、时间键不发查询、unsafe 计数的单测;test_catalog_semantics 加跨版本
用例(把剖析备注改回旧说法写进目录、同样的数据重新剖析,版本号和目录都不变;去掉规整回跑两条失败)。
2026-10-16 12:02:41 +08:00
ThearchyHelios 881c8ef65b 列表中间像小计的行:照常导入、要人确认,带「改作小计核对」的修复按钮
为什么:配方没配小计的列表(期 4 起草的、或者某一期才开始出现小计的文件),首列写着「小计」、其余文字列空着的行
以前静默当数据导入,对金额列求和就把小计重复算了进去,核对也看不出来(H2)。细案 E 的复用里列了「recipe_fixes
加一个小计修复按钮」,这里补上它的触发。

- 执行器:没配 subtotals 时,首列以合计字样开头、其余文字和日期列为空、至少一格有数的行,照常按数据导入,收尾时
  合成一条 subtotal_suspect(需确认类,不拒收:确实是数据的,勾确认项照常导入);首列以「累计」开头、别的文字列有字
  的商品名这类数据行不算。
- 修复按钮 declare_subtotals(FIX_KINDS 末尾新增):给这个列表加 rows.subtotals(标签列、开头词取自文件里的合计
  字样),可选只核对或另存成「{表}_小计」;块已配小计、开头词含数字时不提议。
- 确认项 subtotal_suspect:<块>;前端修复按钮文字「改作小计核对」。
2026-10-16 11:16:33 +08:00
ThearchyHelios a137182145 Excel 期 5:列表按期累积、穿插的小计、表格对象起草、CSV 走配方(E)
为什么:每月一份的明细导出是最常见的场景,期 4 之前列表只能每期替换;夹在中间的小计会让规则起草失败;
表格对象这个现成的边界信号没用上;CSV 不能走配方。四项都复用现有的执行器、核对、累积、溯源,不另起一套。

- 列表按期累积:ListBlock.period 二选一——{column, check: within_context} 按日期列分期,试运行多一条结构类核对
  D(每行的日期都在本期统计期内,空着也不行);{synthesize: 统计期} 每行加「统计期起」「统计期止」两列,列表在
  统计期定下来之前就抽完了,这两格先放占位、写库时换成真值。accumulate_blockers 放行绑定了统计期的列表;合计行、
  小计行另存的表各期主键分不开,照旧不能累积;同一订单两期都出现由并集的主键核对拒收。U3 按列表的分期方式核对。
  说明里「按哪一列筛选」用分期列,加的两列有列说明;空行之后文字行的说法补了并集版本(列表能累积以后要用到)。
- 穿插的小计:ListRows.subtotals {label_column, pick, scope: since_previous | group_column, group_column, keep_as}。
  小计行不进数据,每个数字格一条合计格(分段键是块 id + ·小计),核对模块按它所管那组明细的 rowid 区间用 SQL
  求和比对(新 T 编号,标题「小计行按各组明细求和」),整行记进排除的行(reason=subtotal),可以另存成
  reported_total 表(小计项、分组取值、各数字列);数字格全空报 subtotal_blank(结构类);合计行的公式引用了小计格
  时按它管的明细换算。静态校验:标签列、分组列要在列里,按组小计要给分组列,小计和合计的开头词不能互为前缀。
  规则起草把第一列同一个合计字样、下面还有明细的行提议成小计(以前记 stray_totals 失败),框选同一条规则。
- 表格对象起草:工作表有表格对象时按它出列表块:表头行、列范围、数据行、汇总行(映射成 total_row,标签取汇总行
  第一列的文字)都由表格对象给,锚点仍按表头文字、不记坐标;表名优先用表格对象的名字(含数字的不用)。不调用 AI。
- CSV 走配方:新增 csv_grid,按内容认出 CSV(不是 zip、不是 OLE、开头没有 NUL),编码和分隔符复用 tabular 读 CSV
  的那一段,适配成一张名为「CSV」的工作表(SheetScan.path=csv:,WorkbookScan.format=csv);只有写法毫无歧义的
  整数、小数转成数,千分位、前导零、日期写法一律留作文字交给配方的规则。坐标沿用「工作表!A1」(偏离细案的
  R{行}C{列},理由见 csv_grid 的模块说明)。CSV 不做稀疏判定。导入向导和简单上传的结构提示都放行 CSV。
- 每个列表一道题「每期怎么更新」(q_list_period):每期替换 / 按期累积并按某个日期列核对 / 按期累积并加统计期
  起止两列,后两种同时把导入模式改成按期累积、缺统计期的工作表一并配上(问题集合按草稿生成、回答时不变,草稿里
  的列表还不符合累积资格,q_mode 不会出现,所以并成一道题)。确认项新增 subtotals:<块>、list_period:<块>。
- 前端配方面板:列表块多「小计行」(标签列、开头词、管哪些明细、分组列、另存)和「按期累积时每一行属于哪一期」;
  回执「排除的行」认 subtotal。

验证:新增 tests/test_csv_grid.py、test_recipe_p5.py(执行器、核对、校验、资格、并集物化、起草)、
test_recipe_p5_e2e.py(经接口:三份月度 CSV 累积、部分重叠拒收、同一期替换要确认;带穿插小计的 Excel 起草、
核对、提交)。去掉小计识别、统计期回填、D 核对分别回跑,新测试 10 条失败。旧测试里「夹在中间的小计一律失败」
「列表只能每期替换」的断言按新行为改写。
2026-10-16 11:09:39 +08:00
ThearchyHelios 74909191f7 judge.py:每日计数一节的函数前补一个空行 2026-10-16 11:08:25 +08:00
ThearchyHelios 96af448fc5 全量回归抓到的两处:裁判那本账留回 judge._today 这个日历名字;冻结表结构快照时 ctx 不一定带目录视图
- engine/judge.py:裁判的测试(test_judge_settings、test_verify_spend)换 judge._today 来模拟裁判这边跨天。上一版把它去掉、让三本账都只认 spend._today,等于改了这些测试的前提。改成裁判那本 Ledger 实例放在 judge.py(judge_spend,键不变,日历是 judge._today,它缺省就是 spend._today);spend.py 只放核查、评测两本。test_evidence_judge 恢复原样,test_spend_ledger 跟着改:换 spend._today 三本一起滚,换 judge._today 只动裁判那本
- tools/datasource._store_schema:按 getattr 取 ctx.catalog_view——几处测试(和以后的调用方)直接拿一个只有 run_id / node_id 的对象调它,没有这个属性就是缺省视图
2026-10-16 11:04:41 +08:00
ThearchyHelios ee6e07959b 问数据一轮的「决定」下沉到后端 chat/turn.py,问数据页照后端给的走(细案 01 做法 1,总方案 2.1);这一轮的花费 = 助手 + 运行 + 复核
- chat/turn.py:next_step(有 error 级问题时自查过就判失败、没自查过的老服务端才重来一次;autorun=false 只建不跑;reply、只有反问直接收尾;其余运行)、fill_input(规则同 chat.ts 的 fillInput)、retry_after_review(retry、broken、这一轮还没试满)、retry_instruction(两种措辞原样搬过来)
- answer_ops 的 final(含只问不改那条)带 next(run | ready | fail);/copilot/review 的结果带 retry_instruction(ReviewIn.attempt 按这一轮试过几次判,要重来是那句话,不重来是 null)
- 前端 store/chat.ts:final 照 next 走,复核照 retry_instruction 走(原样交回给助手),launch / watch / settle 把 attempt 带到复核;老后端没有这两个字段时退回原来的规则(常量和措辞留在 retryInstruction 里,和后端逐字对照)
- 这一轮的用量:usage 操作累加进 copilot,复核结果的 usage 累加进 review,自动重来和用户点重试换下来的运行并进 earlier,当前运行的抄进 run,一起随 meta.usage 落库;问数据页轮次头部写这一轮的花费(runCostText:估不出的写「≥」或「金额无法估算」),悬停分得清助手建图、运行、复核,文案在 lib/terms.ts 的 TURN_COST_TEXT;ReviewResult 类型补 retry_instruction、usage
- 新增 tests/test_chat_turn_policy.py(各条决定的分支;前后端对照 MAX_ATTEMPTS、认问题字段的正则、两种重来措辞——改一个字回跑会失败;复核按 attempt 给不给 retry_instruction);test_copilot_answer_ops 的基线比对改成「final 只多一个 next」
- check-chat 补一段「新服务端」:照 next=ready 不发起、照 retry_instruction 原样重来、第二次复核带 attempt=1、meta.usage 四份都记下、头部写 $0.067 且悬停分得清三部分
2026-10-16 10:59:15 +08:00
ThearchyHelios 7e4c3c58a0 自由形蓝图(前端):「谁来做」卡片按服务端的 derived_people 摆,只定每一步由谁做
为什么:服务端已按导入的图列出「谁来做」(自由形蓝图后端那个提交),卡片要照那一份摆出来,发布摘要里「你来定」才指得到一张
真实存在的卡片(05-已知遗留 (d))。

做了什么:Blueprint 带 derived_people(typesBlueprint.DerivedPerson)。BlueprintCards 的 people 卡片对自由形按它一行一行画:
行名、每处用途只读;岗位行和「写入结果不明时谁去核实」一行复用 ItemRow 的人或组编辑(改了发 bp_set 到那一行的 path);发起人、
表达式、外部系统回调的步骤只读;不能加减步骤、不改会签和时限(卡片上写明在画布上改)。卡片上的问题按那几行的 path 归进来。
termsBlueprint 加四句。服务端 blueprint_out 对没有 graph 属性的对象(契约测试里的替身)按没有图算。
2026-10-16 10:54:30 +08:00
ThearchyHelios b334f58009 导入清单的「是否认证」界面照记下的说:登录身份 / 未认证署名
为什么:服务端开始如实记「是否认证」之后(导入清单那个提交),界面上的清单、导入记录、接受理由还一律写「署名(未认证)」,
开了登录、署名就是登录的人时说错了(05-已知遗留 (c))。

做了什么:导入记录接口带上 signed_by_verified(按这一期导入清单里记下的;旧清单记的是 false,照记下的说,不改写;简单导入
没有清单按未认证)。ManifestBody 的文件署名、接受理由的署名,VersionsDialog 的导入记录、快照里各期、接受理由的署名,都按记下
的值写「登录身份:x」或「署名(未认证):x」,和确认框里那一行(signerLine)同一套说法。接受理由由提交这一期的人写:署名和
清单(导入记录)的一样时才算认证。统计期、修改记录的署名是暂存时记的,没有记是否认证,照旧写「署名(未认证)」。

测试:test_signer_verified 加一句:开登录导入后导入记录的 signed_by_verified 为真。
2026-10-16 10:54:30 +08:00
ThearchyHelios fbb671a96b 使用者选人(前端):选人控件按池边输入边取候选;个人渠道的实例事件改叫「我发起的实例完成 / 失败」
为什么:使用者读不到名录,选人控件只能凭记忆手填用户名,填错了服务端以前也照收(05-已知遗留 (b))。服务端已按用途给候选池、
开登录时按同一个池核对(上上个提交),前端要把候选给到人手上。另外个人渠道勾「实例完成 / 失败」以前永远收不到,服务端修好之后
(驳回那个提交里的 notify_run),名字要照实写成只推自己发起的实例(04-对外集成与触达 B1)。

做了什么:
- PrincipalPicker 加 source(CandidateSource:form 带 app_id+entry 或 task_id 和 field,review)。搭建者及以上照旧用整份名录;
  使用者开了登录、给了 source 时边输入边按开头取候选(250 毫秒防抖),列表里显示显示名、标签悬停给用户名;加的时候按同一个池
  问一次服务端,不在池里的当场说「「x」不在可选的人里」(问不到就先收下,提交时由服务端判);池要先输开头才列、列不全时照实提示。
- SchemaForm 按表单用在哪(入口、待办,同上传文件的上下文)给选人控件 source;个人应用提交审核用 purpose=review。
- typesApp:CandidateSource、Candidates、FormField 的 x-principal、FormError 的 principal 码(和服务端 FORM_ERROR_CODES 同序)。
- api.identity.candidates。termsApp 的 PRINCIPAL_TEXT 加三句。
- 设置 → 通知:范围是「仅自己」的渠道,run.finished / run.failed 写「我发起的实例完成 / 失败」(termsApp.NOTIFY_PERSONAL_EVENT_LABEL)。
- 服务端 x-principal 写法不对时的说法改成不带半角冒号接中文的写法(check-copy)。
2026-10-16 10:54:30 +08:00
ThearchyHelios 2fa2db3ea0 驳回不再记成失败(前端):终态按 end_reason 写「被驳回」,副标题写谁、在哪一步、备注
为什么:服务端已把人的驳回收成 cancelled + end_reason=rejected(上一个提交)。前端以前靠正则解析报错原文认驳回,而且只认
「人工驳回」一种,动作确认、工具调用审批、代码执行审批的三种原话都落到兜底、给出注定再失败的「继续运行」(05-已知遗留 (a))。

做了什么:
- types.ts:Run.end_reason(rejected / stopped)。
- lib/status:新增状态 rejected(「被驳回」,同取消的剪影、不画红、终态),resolveStatus 按 endReason 区分;记录页列表、详情、
  航迹的终点按它写「被驳回 · 原话」。
- run/decode:run.cancelled 带 reason=rejected 时标题写「被驳回」,副标题「张工 驳回了「付款」:金额不对」(超时驳回写按设置
  驳回、不重复备注),指向那一步;手动停止、老事件照旧「已取消」。
- store/chat + ChatPage:终态里被驳回的一轮照 cancelled 收(不给继续运行),徽标写「被驳回」,补救区说明这是审批人的决定、
  副标题写谁和备注;刷新之后从 meta.rejected 恢复;核对状态时按运行的 end_reason 认。
- lib/explain:正则只留给存量 failed+驳回,补上「未确认(被驳回)」「的调用已被驳回」「代码执行已被驳回」三种原话,一律不给继续运行。
- 继续运行被拒的说法改成「被驳回的运行不能继续运行:改好内容后重新发起」(check-copy 不收「接着跑」)。
- check-decode 加一节:reason=rejected 的标题、副标题、没署名没备注、超时驳回、相位、手动停止和老事件。

偏离:问数据的「之前的尝试」(retry 留档,W1-a 的地盘)里被驳回的那一次仍写「已手动停止」,没动。
2026-10-16 10:54:02 +08:00
ThearchyHelios ba28dee1f0 生成过程抽成 answer_ops、复核抽成 review_of_run,助手建图和复核的用量也记下(细案 01 做法 2、3):评测和问数据页走同一条生成路径,这一轮的花费不再低报
- api/copilot.py:event_stream 原样搬成 async generator answer_ops(payload, *, catalog_view=None),交出的 op dict 不变;开流前的准备(数据源、目录检查器、助手模型、历史、查看服务)用自己的短会话,在第一条操作之前做完。SSE 路由只包一层:先取第一条,开流前的拒绝(模型没配好、限定的数据源都不在)照旧是 HTTP 错误
- catalog_view 一路传给查看服务和自查的 SQL 检查器(目录视图接缝的「生成过程从参数读」)
- providers/usage.py:Meter 是一个 LangChain 回调,和 get_usage_metadata_callback 同一个机制(登记过的上下文变量),按模型累计 tokens、次数,金额按价格目录;估不出的记 unpriced_calls。没直接用 get_usage_metadata_callback:with 块跨 yield 会把变量留在调用方上下文里,调用方随后发起的运行会继承它、被重复计一遍;metered 只在每一步 __anext__ 期间挂上,嵌套时并进外层
- 生成过程读到 done / look / ask 就关模型的流(后面的闲话不要),流末尾才报的用量拿不到:这一次照样计次数和已报的 token,记成估不出,不悄悄少算(遗留:真实接入上建图那几次多半是下限)
- providers/budget.py:BudgetFallback.astream 用 aclosing 包住里面那条流——以前调用方关流时里面那条要等垃圾回收才关,模型那次调用的收尾(用量回调)落在算完账之后;测试去掉它回跑会少计一次
- answer_ops 末尾多一条 {op: usage, by_model, input_tokens, output_tokens, calls, cost_usd, unpriced_calls};/copilot/review 拆出 review_of_run(问数据页、评测共用),结果带 usage(没调模型时全 0)和 retry_instruction(要不要带着原图重来、说什么,按 chat/turn.py 的决定算;ReviewIn 多一个 attempt)
- 新增 app/chat/turn.py(问数据一轮的决定,复核这边先用上;final.next、对照测试、前端照做在下一个提交);新增 tests/test_copilot_answer_ops.py(八种结局的 SSE 输出和抽出来之前录下的 fixtures/copilot_answer_ops_baseline.json 逐条相同,末尾多一条 usage;直接迭代和 SSE 同一串;开流前的拒绝仍是 HTTP 错误)、tests/test_usage_meter.py;读 SSE 的十一个测试文件的收流帮手跳过末尾那条 usage
2026-10-16 10:45:45 +08:00
ThearchyHelios 3293ac0b58 自由形蓝图(后端):按导入的图列出「谁来做」,只定由谁做;没绑时 bp.people_unbound 指向那张卡片
为什么:自由形的骨架就是导入的图,图里派给岗位的人工节点、没登记查询的写入在应用里都要绑人,可蓝图上没有「谁来做」卡片。
第一次发布挡在 app.slot_unbound / app.reconciler_unbound 上,白话叫人「在「谁来做」里给这一步选人」,卡片却不存在;这时
应用还没建,也没法去应用编辑器绑,只能绕开蓝图(05-已知遗留 (d))。

做了什么:
- freeform 的 cards 加 people(「可选:只定每一步由谁做;会签、时限、超时在画布上改」)。freeform.people_rows 按 tree.slot_refs
  列岗位行(同一个岗位用在几处合成一行,label 用节点标题拼、detail 写全每一处用途,path 是 /people/<i>/who);写入能力没登记
  查询时加一行「写入结果不明时谁去核实」(path /writeback/reconciler);发起人、表达式、外部系统回调的步骤列成只读行。
  blueprint_out 给出 derived_people,界面照这一份画。
- 存储复用现有路径:岗位绑定存在 spec.people[](id 就是岗位名、只写 who),_common.slot_of / bindings_of 原样生效;核实人存在
  /writeback/reconciler。needs_reconciler 对自由形按图里的写入算(with_graph 把蓝图存的图带给模板函数)。
- bp_set 对自由形的 people 只收 who(只收人和组,只由用户定),改会签、时限、超时、加减步骤报 bp.template_mismatch「在画布上
  改」。apply_bp_op / blueprint_issues 新增 graph 参数,卡片接口、蓝图检查、试运行、实例化、蓝图助手都把蓝图存的图传下去。
- 检查:派生岗位、核实人没绑报 bp.people_unbound(试运行 warning、发布 error),路径就是那一行,带 slot;图里已经没有的岗位、
  卡片上还留着的绑定报 warning,不静默删除。发布单照核实人的办法,去掉同一件事的 app.slot_unbound(slots.<slot>)和
  app.reconciler_unbound。

偏离:岗位行不让助手代填(同「结果不明时谁去核实」,派活给谁是人的决定)。

测试:test_publish_plan 新增「财务审批(岗位)+ 写回(没登记查询)」建自由形蓝图一条(两行、不选人时只有 bp.people_unbound、
改会签被拒、选好人后绑到 finance 和核实岗位、发布单不再挡);老的两条按新说法改;test_blueprint_templates 新增 people_rows
的合并、升级岗位、只读行、残留绑定提醒。
2026-10-16 10:34:18 +08:00
ThearchyHelios d14fc97da7 应用入口、按需视图的表单没通过校验也带 code(form_invalid),和待办回复、入站钩子同一个码
api/apps.py 里这两处直接回 JSONResponse(detail + errors),上一次补错误码时只查了 HTTPException,漏了它们:外部系统发起
实例时表单不对,仍然只能按中文句子认。detail 和 errors 不变。

tests/test_integration_errors.py 的静态检查跟着扩:这几个模块里直接回的 JSONResponse 错误,内容里也要有 code(改之前
这条检查报出了这两处)。
2026-10-16 10:33:41 +08:00
ThearchyHelios 4c0e83bc80 目录视图接缝 data/catalog_view.py(细案 01 做法 5,总方案 X7):读目录的四处统一经它,「开目录 / 关目录 / 只用已核实的项 / 目录快照」的对照才做得了
- CatalogView{mode: live | snapshot | off, statuses: all | reviewed, snapshot}:缺省 live + all,行为和以前一样;reviewed 只留已核实、已确认的项(驳回的项原样留着交给各处,照旧经 visible_notes 去掉,冻结快照和 SQL 检查靠驳回的关系编号认出不该现推的关系——为此在 catalog.py 加 restrict_notes,没给 visible_notes 加参数)
- 视图从哪来:运行从 Run.options['catalog_view'] 读(catalog_view.of_run:RunContext.extra 里的发起快照,审批恢复、续跑沿用),ToolContext 多一个 catalog_view,节点(调用工具、llm/agent、多 agent)构造时带上;生成过程从参数读(下一个提交接 answer_ops)
- 四处消费一律经它:运行时 db_schema / db_query(tools/datasource._catalog_of,冻结进表结构快照的也是这一份)、look 表详情(TableKind.load、copilot_context.look_service 收 catalog_view)、自查的 SQL 检查器(sqlcheck.load_checkers 收 catalog_view,改用 catalog.read_catalogs 一次读出)、双盲复算(_Sources 带视图,交给盲算的目录和比较 SQL 用的中文名都按它;核查 agent、反方审稿的 db_schema 也按这次运行的视图)
- 冻结快照:不是缺省视图的在引用 meta 里记下 catalog_view(不进内容哈希,缺省视图不记,快照和以前一字不差),证据面板据此写得出「本次运行目录:关」
- snapshot 模式只定读法(kind=eval_catalog_snapshot 的工件,{sources: {源 id: {tables: {表: {version, notes}}}}}),内容由第 2 波的题集包写;快照不在了照实报错(CatalogSnapshotMissing),不悄悄退回当前目录
- 新增 tests/test_catalog_view.py:四种视图在四处给的是同一份、缺省等于当前目录、运行从 options 读且冻结 meta 记下、lint(消费模块不许直接 read_catalog / read_entry / 查 CatalogNote);去掉 _catalog_of 里的视图回跑两项失败
2026-10-16 10:28:01 +08:00
ThearchyHelios ee7b8b2f97 剖析与探查的方言修正(C1)和没有真库时的方言验证(C2)
为什么:剖析是全站唯一主动查业务库的地方,以前只在守卫放行和字符串片段两层上测过,从没在真库上跑过。
本机四个容器(PG 16、MySQL 8.4、MariaDB 11、Oracle Free 23)实测,旧的 IN 列表写法在 PostgreSQL 上整数子列
对文字主键直接报 operator does not exist: character varying = integer,关系一条都核对不了。

C1 方言修正:
- IN 列表的字面量按被指向列的类型族写(key_literals):父列文字写 '12',父列数字只收整数写法的值,父列是时间、
  对不上的不拼并在备注里写「两端类型不同」;子表是 CHAR(n) 时去掉补齐的尾随空格。
- 列类型改存按方言编译的完整写法(VARCHAR2(20 CHAR)、NUMBER(10, 0)、ENUM('a','b')、TIMESTAMP WITH TIME ZONE),
  编译不了退回 str()。SQLAlchemy 把 Oracle 的 VARCHAR2 反射成通用 VARCHAR(容器实测),照数据字典写成 VARCHAR2。
  ENUM / SET 另记 enum_values;Oracle 小写带引号的表和列记 quote_name;各索引(含主键)的首列记进
  index_first_columns(下一波取值观察判断能不能对大表发窗口查询)。新探查的快照哈希会变,旧运行封存的不受影响。
- catalog._type_family 认 ENUM / SET 是文字、INTERVAL 不是数字,去掉引号里的取值再认;新增 column_family
  (有 enum_values 的是文字,PostgreSQL 枚举编译出来只有类型名);is_date_column 不再把 ENUM('date') 认成日期列。
- 码值候选:ENUM / SET 不看列名直接算候选,ENUM 列定义里这次没出现的取值也写进码值(含义待填);VARCHAR2(20 CHAR)
  的长度按 20 算;CHAR 码值去尾随空格;MySQL 上写明按排序规则分组、比较(可能不分大小写)。
- 统计信息估出的行数不超过整表统计上限时再跑一次有上限的计数确认,统计过期的大表不再被当成小表做整表统计。
- 日期列的最小值、最大值只看 0001-01-02 到 9999-12-31 之间的值,零日期、占位日期、PG 的 ±infinity 不计入,
  照实写「另有 N 个无效日期未计入」,日期范围里带 invalid 个数(界面显示)。
- 新增取值观察用的方言方法 date_literal / day_trunc / sample_from,以及按日行数、时间窗口里的取值分布、块采样三类
  查询的拼法(窗口上端截在 9999-12-31 之前,不让 infinity 多出一天)。
- 说明文字不再说「抽样」:大表看的是「按存储顺序取前 N 行」;设置项「抽样键值数」改叫「核对键值数」。

C2 验证:
- tests/test_profile_dialect_golden.py:五种方言 × 全部剖析和观察查询的完整 SQL 钉在 tests/fixtures/dialect/,
  sqlglot 逐条解析、过只读守卫,再用 qualify + annotate_types 核对 IN 两边类型族一致(附旧写法的对照)。
- scripts/dialect-it.sh(不进 CI):本机起四个容器,建虚构夹具(保留字列、Oracle 小写带引号的表、CHAR 码值、
  MySQL ENUM、零日期 / infinity / 占位日期、做旧的统计信息、超上限的大表、带索引的日期列),用 AgentLab 自己的
  探查和剖析代码跑全部查询,断言零报错、结论和手算一致。四种库都通过;MariaDB 插入时自己更新行数估算,统计信息
  做不旧,照实打印。把 IN 列表改回旧写法回跑,PG 上照样报出上面那条错误。跑完删容器、留镜像。
2026-10-16 10:25:28 +08:00
ThearchyHelios a59409cc85 使用者选人(后端):按用途给候选池,开登录时入口表单、待办表单的选人控件按同一个池核对
为什么:使用者在选人控件里没有候选(名录只给搭建者),服务端也不核对。填错的名字原样派下去,待办给了一个不存在的人,
没人能处理、也不报错(05-已知遗留 (b))。名录又不能整份放开给使用者(只读与最小可见)。

做了什么:
- auth/directory 新增候选池:form 用途缺省是「能用这个应用的人」——使用及以上的授权(人;组列出组本身并展开成员),
  加上管理它的人(管理员、建应用的人);应用授权给了所有人时池是全部启用的人(open),不带前缀 q 不列(needs_query)。
  字段可以声明 x-principal: {pool: app | group:<组> | slot:<岗位>}(slot 按这次用到的应用版本的 bindings 展开)。
  review 用途是启用的搭建者和管理员(同 personal._review_targets)。停用的人一律不列。
- GET /api/directory/candidates(任何登录的人;main.py 路由清单只加一行):purpose=form 带 app_id+entry 或 task_id、field,
  调用的人不能用这个应用 403;只回 {users:[{username, display_name}], groups, truncated, needs_query?},不给停用、角色、
  邮箱、组成员;q 按用户名或显示名开头筛,limit 最多 50。/api/directory 仍只给搭建者。
- 开了登录时 start_entry 和待办表单提交按同一个池核对,不在池里 422,errors 的 code=principal(FORM_ERROR_CODES 加一项),
  话写成「「x」不在可选的人里,请从候选中选择」(单点登录另说要先登录一次才进名录)。直接生效(遗留-Q4 按推荐)。
  关登录照旧手填、不核对;不属于应用的待办(试运行、画布)没有池,不核对。
- apps/forms:x-principal 进 _FIELD_KEYS,validate_form_schema 查写法(只能写在选人控件上、只能是三种池)。

搭建者没有授权时按 apps/access 只能「查看全部实例」,不算池里的人——池按「能用」算,这一点写进了 app_pool 的说明。

测试:test_directory_candidates(只给名字、停用的不列、前缀筛、没使用权 403、名录仍 403;授权给所有人要前缀、截断;review
只出搭建者和管理员;入口表单不在池 422、停用的 422、组名和池里的人放行;待办表单同一个池;声明 group:<组> 收窄;关登录
任何名字照收;x-principal 写法校验)。
2026-10-16 10:24:22 +08:00