Repository navigation
datasource 账本判定的 20 条死键至今无人处置:三个块整块无人读,其中 readOnly 让一个 shipped 示例的「只读副本」可写(ADR-0049 enforce-or-remove) #4583
Description
Activity
- added a commit that references this issue
on Aug 2, 2026 四批全部完成,
datasource死键 20 → 0。批 内容 PR A capabilities11 键 + 整块(含DriverDefinition.capabilities)#4601 ✅ B/C/D retryPolicy×4、healthCheck×3、external.label/external.requirePermission#4629 ✅ 全部走 remove,理由是同一条
九个(+11 个)键没有一个是「用得少」——是每一个都已经有另一套 live 机制在做它看起来在配置的那件事。下推判断读运行时 driver 自己的
supports.*;连接失败归启动策略(降级启动 /bootCritical);存活是 driver handle 的ping()/checkHealth()按需探测;联邦显示名是顶层label;联邦访问由普通对象权限集 + RLS 管。对面都已经有人了,所以没有可建的桥。这和同批 #4509 里
email_template走 enforce 是同一个判据的两侧:看的是「这个形状能不能承载这个功能」,不是「它是不是死的」。两处值得记下来的
capabilities.readOnly(批 A) —— 它读起来像安全属性,也确实被当作安全属性写:showcase 里一个标着 "CRM Analytics Read Replica" 的 datasource 就靠它声称只读,而它照常接受写入。这个键已经被搬过两次(#4410 移出config,#4465 移进capabilities),每个地址上都是惰性的。这次是删掉,而不是搬第三次。同时改掉了所有 SQL driver 共享的READ_ONLY_BELONGS_ON_DATASOURCE—— 它当时仍在把作者指向这个键。retryPolicy(批 B) —— 报错刻意不提供改名建议。hook.retryPolicy/job.retryPolicy是真被强制执行的,但它们是另一个类型上的另一个键,延迟拼作backoffMs而非baseDelayMs。而这个拼写不一致本身就是「没人读 datasource 那个」的证据——全仓没有代码同时读两种拼法。有测试钉住报错文案必须同时包含backoffMs与hook/job:一条把作者引向另一个类型同名键的处方,比没有处方更糟。刻意没做的事 → #4584
删掉
readOnly并没有给作者替代方案。external.allowWrites: false是唯一被强制执行的写闸门,但assertWriteAllowed对 managed(或未声明schemaMode)的 datasource 直接放行——本地库目前根本没有只读闸门。按 #4479 的先例,退役 PR 不就地发明机制:这个缺口立为 #4584,并在墓碑文案里明说它不适用于哪种情况、建议改用数据库账号 GRANT。把作者从一个惰性键指向另一个惰性键,只是把本次要退役的缺陷洗一遍再发出去。
沿途修补
objectql的metadataArrayKeys缺emailTemplates(#4488 审计发现的四个"授权门断连":email_template / job / validation 的元数据条目到不了执行点,action 导航项点不动 #4509 那批发现,同源问题)- 一个测试写
external: { label: … }只为让块非空,现在写external: {} - CLI 的
lint-liveness-properties.test.ts跑在真实 shipped 账本上,所以每次删 datasource 账本行都会反转它的一条断言。它在两批里各翻了一次,现已收敛成断言零发现并保留作回归钉——将来谁重新引入一个 dead+authorWarn 的 datasource 属性,会在这里红,而不是悄悄发出去。
关闭。
Generated by Claude Code
- added 11 commits that reference this issue
on Aug 3, 2026 - added 3 commits that reference this issue
on Sep 28, 2026
#4487 把
datasource纳入活性账本时判了 43 条,其中 20 条 dead,并给每一条写了authorHint。那次交付的是判定;处置没有人接,至今没有 issue 跟踪。这条就是那个位置。对比一下同类死键的处置都是一簇一条 issue 立的 —— #3207(
object.enable.trash/mru)、#4484(IDataDriver.findStream)、#4579(openApi31)、#4391(crypto.hash)。datasource 这 20 条是全仓最大的一块,也是唯一 20 条全部带authorWarn的(全平台 75 条 dead 里只有 29 条带告警),却是唯一没立项的。处置方向已经定了:全部 remove
20 条的
authorHint无一例外都是 "Delete it"。这不是本 issue 新做的判断,是 #4487 逐条闭合调用图后的结论 —— 没有一条走 enforce 路线,因为每一条都有一个已经在工作的、不同的机制:capabilities.*supports.*(objectql/src/engine.ts:3671, :4529, :4810),和 datasource 元数据是两套机制retryPolicy.*datasource-connection-service.ts的启动策略(降级启动 /bootCriticalfail-fast),不按计划重试healthCheck.*ping()/checkHealth()(contracts/datasource-driver-factory.ts:88-92)external.label/external.requirePermissionlabel;后者的访问控制走普通对象权限集 + RLS三个块会整块清空,这比「20 个键」更干净:
DatasourceCapabilities(11/11 死)、healthCheck(3/3 死)、retryPolicy(4/4 死)作为 schema 块整体消失,external块留下但少两个键。最严重的一条:
readOnly已经在 shipped 示例里造成了它声称要防的那个 bugexamples/app-crm/src/datasources/crm.datasource.ts里有一个叫CRM Analytics Read Replica的 datasource:它上面的注释自己写着(#4410 留下的):
搬家没有修好它。 #4410 把
readOnly从config(无闸门)挪到capabilities(有 schema 闸门但仍无人读),注释描述的那个 bug 原封不动地活到今天:这个「只读副本」现在照样可写。而且这里有个真实的能力缺口,不能靠改处方绕过
账本的
authorHint给的替代是external.allowWrites: false(objectql/src/engine.ts的assertWriteAllowed)。但读一下那个闸门:它只对 external / 联邦 datasource 生效。 crm 那个是本地 sqlite、没声明
schemaMode⇒ 落进managed默认 ⇒ 闸门在第 1904 行直接放行。也就是说:对一个 managed datasource,目前根本不存在任何被强制执行的只读机制。
capabilities.readOnly是唯一长得像的那个,而它什么也不做。删掉这个键是对的(它是假合规),但要同时诚实地承认:这不是把作者指向另一个键就能了结的,那个键对他的场景不适用。建议本 issue 只做删除 + 把示例改成不再声称只读,并另立一条 feature issue 讨论 managed datasource 要不要有只读闸门 —— 参照 #4479(
readReplicas移除后另立读写分离立项)的先例,不要在退役 PR 里顺手发明一个机制。影响半径(已实测)
DatasourceCapabilities全仓引用只有datasource.zod.ts自己两处(:370 driver-definition、:544 datasource)+ 它自己的datasource.test.ts。零运行时消费者,判定可复核。app-crm示例(capabilities:)。healthCheck/retryPolicy无人 authoring。content/docs/data-modeling/*.mdx零命中)。healthCheck/retryPolicy在 docs 里的命中全是别的类型的生成参考页(model-registry、plugin-rest-api、events-queue/bus、startup-orchestrator)—— 就是 活性账本覆盖 worklist:9 个已注册 metadata type 仍未治理(#4487 建立闸门后的剩余债务) #4488 点名的「同名不同类型」陷阱,不要照着改。DatasourceCapabilitiesType是导出类型,在公共 API 面上 ⇒check:api-surface会参与。路线:strict-removal(账本行删除,不是墓碑)
三个块全部是
.strict()(datasource.zod.ts:427/551/568),按.claude/skills/spec-property-retirement§2 走删除 +*_RETIRED_KEY_GUIDANCE处方路线 —— 键离开 walked shape,所以账本行要一并删掉(留着会被orphans.mts判 ORPHAN)。反过来(留墓碑却删账本行)会判 UNCLASSIFIED。两个方向都会红。DatasourceCapabilities整块消失时,driver-definition上那处capabilities(:370)需要单独决定:它不在本账本治理范围内(账本治的是datasource),但用的是同一个 schema。判它之前先闭合它自己的调用图,不要因为 datasource 侧判死就顺手判死。建议的批次(可并行认领)
capabilities.*11 条 +DatasourceCapabilities整块 + app-crm 示例retryPolicy.*4 条(整块)hook.retryPolicy/job.retryPolicy,那两个是被强制执行的,且拼写不同(backoffMsvsbaseDelayMs)—— #4488 点名的最险的一个坑healthCheck.*3 条(整块)external.label/external.requirePermission2 条每批一个 changeset,FROM → TO 写清楚。批 A 的 changeset 必须明说:删除
readOnly不等于给了替代方案,managed datasource 的只读能力是另一条 issue。验收
*_RETIRED_KEY_GUIDANCE给出处方packages/spec/liveness/datasource.json对应行删除(不是改状态),README 计数按现有口径重算check:liveness/check:api-surface/check:generated/ 全仓build+test绿lint-liveness-properties的 datasource 相关期望更新(20 条告警归零)参考
readOnly挪进capabilities的那次.claude/skills/spec-property-retirement§2(路线分叉)、§4(14 面清单)