Skip to content

settings 消费缝丢弃 ResolvedSettingValue.source —— 服务无法区分「管理员写过的值」和「schema 默认值」 #5536

Description

@os-zhuang

分诊来源:核对 #4968 时的诊断副产物(该单的根因分析见 #4968 的核对评论)。observation-class:除 storage 一处已有具体受害路径(在 #4968 里处理)外,其余消费者今天没有已证实的用户可见故障,故打 finding、不进 pm:queue,留给 PM 分诊定级。

事实

SettingsService.get() 解析出的 ResolvedSettingValue 带三个字段:valuesource('env' | 'global' | 'tenant' | 'user' | 'default')、cascadeChainsource 是 spec 里的一等字段:

  • packages/spec/src/system/settings-manifest.zod.ts:461source: z.enum(['env','global','tenant','user','default']).describe('Resolution source')
  • packages/spec/src/system/settings-client.zod.ts:51 的注释明说它的用途:"tells the consumer which scope row was actually mutated; this allows finer-grained reaction"

每一个消费缝都只取 .value,把 source 丢掉:

位置 代码
packages/services/service-settings/src/settings-service.ts:472 for (const [k, v] of Object.entries(payload.values)) raw[k] = v.value;(snapshotOf,即 createClient 快照的来源)
packages/services/service-storage/src/storage-service-plugin.ts:397 values[k] = v?.value;
packages/services/service-sms/src/sms-plugin.ts:157 values[k] = v?.value;

plugin-email(packages/plugins/plugin-email/src/email-plugin.ts:306)走 createClient,因此经由 snapshotOf 同样看不到 source

为什么这是缺陷,而不只是一个没用到的字段

丢掉 source 之后,消费者无法回答一个它必须回答的问题:这个值是有人真的配过,还是根本没人配、它只是 manifest 的 schema 默认值?

两者在 .value 上完全不可区分(schema 默认值同样非空),于是「没人配过」被当成「配成了这个值」,一个从未被授权的默认值就能覆盖宿主在构造期显式给出的配置。storage 是已证实的受害路径:--fresh 传入的 uploads 根目录被 storage.local_root 的 schema 默认值顶掉(实测证据与完整链路在 #4968)。

同一形状的判据在 storage 里被写成了「值非空」:

const hasAny = Object.values(values).some((v) => v !== undefined && v !== null && v !== '');
if (!hasAny) return;

紧邻的注释写的却是 "No persisted values yet → keep the constructor-built adapter" —— 注释说 persisted,代码测 present。它真正想表达的判据是 source !== 'default',而这个信息在上面三行就被丢掉了。

建议方向(不在本单实施)

消费缝保留 source(或至少派生一个 authored: boolean),让 settings-bound 服务能按「被授权过的值」而不是「非空的值」做决策。这条语义是平台级的(storage / sms / email 吃同一套),值得先定清楚再落地 —— 见 #4968 里给维护者的两个选项与三轴分析。

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