Skip to content

managed datasource 没有任何只读闸门:external.allowWrites 只管联邦库,本地库声明只读无处可写(#4583 移除 capabilities.readOnly 后的正式立项位) #4584

Description

@os-zhuang

#4583 拆出,照 #4479 的先例:退役 PR 负责删掉假合规的键,不负责就地发明一个机制。

背景

datasource.capabilities.readOnly#4583 里被移除——它读起来像安全属性,实际没有任何写路径读它,一个标着「只读副本」的 datasource 照常接受写入。

移除时给作者的处方是 external.allowWrites: false但那个处方只覆盖一半场景。

缺口

ObjectQLEngine.assertWriteAllowedpackages/objectql/src/engine.ts):

const ds = this.datasourceDefs.get(dsName);
// No recorded definition, or an explicitly managed one ⇒ allow.
if (!ds || !ds.schemaMode || ds.schemaMode === 'managed') return;

const dsAllows = ds.external?.allowWrites ?? false;
const objAllows = object?.external?.writable ?? false;

闸门在第一个 if 就对 managed(以及没声明 schemaMode 的)datasource 直接放行。external.allowWrites联邦所有权闸门——它回答的是「这个外部库归谁写」,不是「这个连接是不是只读」。

所以对一个本地的、managed 的 datasource:

  • capabilities.readOnly —— 已删除,从来没生效过
  • external.allowWrites: false —— 不生效(在 managed 分支就返回了)
  • 没有第三个选项

examples/app-crm 里那个 crm_analytics(sqlite、无 schemaMode、label 写着 "CRM Analytics Read Replica")就是这个形状:#4583 之后它不再声称自己只读,但也依然无法只读。

要请的是决策

A. 建闸门。assertWriteAllowed 认一个与联邦无关的只读位。设计要点:位放在哪(datasource 顶层 readOnly?复用 schemaMode?)、和 external.allowWrites 的关系(两个都要过,还是短路)、以及 isSystem / 迁移 / DDL 是否豁免——一个连 schema sync 都拦的只读库多半不是作者想要的。

B. 不建,明确记录。 承认「只读连接」不是平台提供的能力,只读靠数据库账号权限做(GRANT SELECT),并把这句话写进 datasource 文档。这也是一个诚实的答案——而且很可能是诚实的那个:数据库侧的只读账号是真闸门,应用层的布尔位不是。

我倾向 B,除非有明确需求要应用层拦截。理由是 #4583 的教训本身:readOnly 之所以危险,不是因为它不存在,而是因为它看起来存在。再造一个只拦 ObjectQL 写路径、拦不住直连 / 迁移 / DDL 的位,会重演同一个形状——只是这次带着一个能跑的测试当背书。

如果选 A,请先回答「它拦不住什么」,并把答案写进 describe(),而不是等下一次审计来问。

参考

未指派 —— 这是决策而非补丁,认领前先在下面说结论。

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions