Skip to content

datasource.config 至今无人校验:驱动 configSchema 是声明但完全惰性的(ADR-0049 enforce-or-remove,#4001 收尾发现) #4410

Description

@os-zhuang

从 #4001 的收尾核查中发现。#4207 把 DatasourceSchema 顶层收紧成 .strict() 时,把 config 作为「逃生口」留开,理由写在 data/datasource.zod.ts 的模块注释里:

config is per-driver by construction … so it stays z.record. The driver's own configSchema is what validates it.

这句话是假的。 它在两个独立的层面上都不成立。

证据

  1. 没有东西去填它。 DriverDefinitionSchema.configSchema 声明为 z.record(z.string(), z.unknown())(data/datasource.zod.ts:284),而仓库里仅有的两个驱动定义都把它设成空对象:

    • data/driver/memory.zod.ts:279 — configSchema: {}
    • data/driver/mongo.zod.ts:67 — configSchema: {}, // Will be populated with JSON Schema version of MongoConfigSchema at runtime

    那句注释承诺的 "at runtime" 填充不存在。

  2. 没有东西去读它。 全仓 grep configSchema 的命中全部属于 automation 的流程节点 descriptor(那个是活的,两回事)。驱动的 configSchema 在本仓没有任何读取点。四个驱动插件(driver-memory / driver-mongodb / driver-sql / driver-sqlite-wasm)也都没有拿任何 schema 去 parse 自己的 config。

  3. 对应的 zod schema 存在,但接在空气上。 PostgresConfigSchema / MongoConfigSchema / MemoryConfigSchema 都在 data/driver/ 里写得很完整(连接串、host、port、pool、ssl…),是纯导出,没有任何消费者。

为什么这个比一般的「声明未执行」更值得修

因为 #4001 的修复主动把作者指引到这个洞里。belongsInConfig 给出的处方原文是:

host is a driver connection detail — it belongs inside config … Move it to config: { host: … }; the driver's own configSchema validates it there.

于是一个把 host 写在顶层的作者——那是现在会报错的位置——被平台以权威口吻,指引到一个同样的拼写错误重新变得静默的槽里。这正是 #4001 要消灭的失效模式,由 #4001 自己的修复重现了一次。

具体后果举例:config: { hostname: 'db.internal' }(正确的键是 host)今天完全静默——z.record 收下它,驱动读不到 host,于是连到 localhost 默认值上。这与 #4001 描述的原始 bug 逐字同构,只是低了一层。

对 AI 作者尤其糟糕:它拿到成功响应,报告「数据源已配置」。

处置

#<PR> 已经先做了止血——把那句假承诺删掉,改成指向 per-driver schema 的诚实措辞,并把 data/driver/ 三个文件补进 #4001 的严格性账本。但那只是不再撒谎,洞还在。

本 issue 追踪真正的二选一(ADR-0049 enforce-or-remove):

  • enforce —— 让 configSchema 真的生效:驱动注册时用自己的 zod schema(或其 JSON Schema 投影)parse datasource.config,未知键按 未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001 的标准配可修错误信息拒绝。注意这条要先回答一个问题:configSchema 的 JSDoc 说它 "Used by the UI to generate the connection form" —— 那是 ../objectui,本仓看不到,需要先确认前端是否真的在读它,否则可能是第三处虚假声明。
  • remove —— 如果结论是驱动 config 就该保持 schemaless,那么 configSchema 字段和三个 *ConfigSchema 导出都属于 ADR-0049 的「声明但不执行」,应当按 spec-property-retirement 走退役流程,而不是继续摆在那里让人以为它管用。

倾向 enforce:三个 schema 已经写好了,datasource 是注册元数据类型,而 config 是唯一一个作者会写、却在收紧之后仍然完全无校验的槽。

参考

Activity

  1. self-assigned this
    on Aug 1, 2026
  2. os-zhuang commented on Aug 1, 2026

    @os-zhuang
    ContributorAuthor

    先回答那个前置问题:前端确实在读一个驱动的 configSchema——但不是这个

    issue 里提的"需要先确认前端是否真的在读它,否则可能是第三处虚假声明",答案是:不是第三处虚假声明,是一处重复声明。

    objectui 的 DatasourceResourcePage.tsx(packages/app-shell/src/views/metadata-admin/datasource/)会 GET /api/v1/datasources/drivers,然后用返回的 configSchema.properties 渲染连接表单——title 作标签、description 作说明、default 作初值、format: 'password' 走密码控件、enum 走下拉,configSchema.required 决定必填星号。JSDoc 那句 "Used by the UI to generate the connection form" 是真的。

    但它读的是 service-datasource/driver-catalog.ts 里的 DRIVER_CATALOG——另一套手写的 JSON-Schema 字面量,和 packages/spec 的 per-driver zod schema 各写各的,互不校验,两边都不做验证。所以 DriverDefinitionSchema.configSchema 确实是死的,只是它旁边还躺着一份活的副本。一活一死,中间没有任何东西对得上。

    处置:enforce

    按 issue 倾向的方向做了。packages/spec/src/data/driver/ 成为唯一契约,三个消费者读它:DatasourceSchema 解析 config(以及每个 readReplicas 条目)、DriverDefinitionSchema.configSchema 发布它的 JSON-Schema 投影、连接表单渲染同一份投影。mysql 和 sqlite/sqlite-wasm 此前根本没有 config 形状,尽管表单在提供、工厂能构建。

    第二道作者入口也补上了:Setup 向导走 metadata.register(只做 name/label 结构检查,不是 zod parse),所以它绕开了 DatasourceSchema。DatasourceAdminService 的 create/update/test 现在查同一个 registry——testConnection 在探测之前校验,否则向导会对着 localhost 报一个绿色的"连接成功"。

    装门禁反过来逼出的东西

    config 一旦有门禁,里面每个键就都在声称自己被读。于是逐个对着读它的代码核了一遍,结果又是一批"声明了但没人读",这次是接上而不是放行:

    • datasource.pool —— 声明过、strict、还被带进了 connection spec,然后被工厂硬编码的 {min:0,max:5} 覆盖掉。
    • datasource.schemaMode —— 在 record → spec 之间被丢掉,工厂只好去两个永远不可能有它的地方找。结果是 schemaMode: 'external' 的库被当作 managed 构建,驱动层的 DDL 闸门是开的。
    • datasource.ssl —— 同样停在 record,带 CA 证书的 TLS 块什么也没配上;这正是它自己 schema 注释里警告的失效模式。
    • mongo password —— 被忽略,所以带 config.password 的 mongo 数据源是用空密码拼 URL 的。

    indexes / maxRecordsPerObject(memory)没有可接的地方——InMemoryDriverConfig 没有这两个字段,驱动不建索引也不淘汰——按 ADR-0049 移除,拒绝信息里带上原因。

    一处中途改回的设计

    config.ssl 我最初建模成 boolean | {ca,cert,key},对客户端接受的形状是诚实的。后来读 objectui 的 renderField 发现:不是 boolean/enum/number 的属性一律渲染成文本输入框——那样向导会写出一个字符串,而新门禁恰好拒绝它,等于造出一个存不进去的表单。所以 config.ssl 收敛成开关简写,证书走数据源级 ssl 块(也就是这次刚接上的那个)。

    分支 claude/datasource-config-validation-i7wdmo 已推送。pnpm test 132/132、pnpm typecheck 122/122、pnpm lint 全绿,spec 的 8 个生成物 gate 全部 up to date,严格性账本的 driver/ 行已从"blocked on #4410"改成 strict。

    工厂里剩下的 4 处未声明 ?? 容错(file/uri/user/connectionString)没有顺手删——删了会静默改变已持久化记录的数据位置,正确路径是 ADR-0087 转换条目,已单独开 #4456 跟踪。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions