Skip to content

composeStacks silently drops every non-array top-level key — api: today, server: as of #4910 #5005

Description

@xuyushun441-sys

发现于 #4910 开发过程(入站 rateLimit seam),与本单修复无关,按 Prime Directive #10 单独立单,未认领

事实

composeStacks(packages/spec/src/stack.zod.ts)只合并三类东西:

  1. manifest —— 按 manifest 策略择一;
  2. i18n —— last-wins;
  3. objects + CONCAT_ARRAY_FIELDS 里列出的数组集合 —— 拼接。

其余一律不进 composed。它从一个空对象 {} 开始逐项填充,所以任何既不是 manifest/i18n、也不在数组清单里的顶层键,合成后直接消失,没有告警。

今天受影响的顶层键:

谁消费它 合成后
api(enableProjectScoping / projectResolution / enforceProjectMembership) objectstack serve(serve.ts),转发给 REST + dispatcher
server(security.rateLimit / trustProxy,#4910 新增) objectstack serve → dispatcher 的入站限流器
datasourceMapping 数据源路由

datasourceMapping 尤其值得一看:它数组,只是没被列进 CONCAT_ARRAY_FIELDS(需实测确认)。

为什么这是 declared ≠ enforced 的形状

作者写下 server.security.rateLimit,单栈下真限流;把同一个栈丢进 composeStacks([base, addon]),限流静默消失,defineStack 不报错、validate 不报错、启动日志也不会说少了什么 —— 因为消费方看到的就是 undefined,与「没写过」无法区分。这正是 #4686 那一类缺陷换了个入口。

api.enforceProjectMembership 走同一条路径,后果是每环境成员 403 闸门在合成栈里悄悄关掉,安全影响比限流更直接。

复现(未跑,依据是源码 —— 请开工时先实测确认)

const a = defineStack({ manifest, server: { security: { rateLimit: { enabled: true, maxRequests: 5 } } } });
const b = defineStack({ manifest });
composeStacks([a, b]).server   // → undefined

注意 composeStacks 的短路:stacks.length === 1原样返回,所以单元素合成看起来是好的,只有真正 ≥2 个栈才丢 —— 这也解释了为什么至今没被发现。

待裁决(不要猜)

顶层标量/对象键的合成语义没有先例可循,需要维护者定一次,而不是每个键各定各的:

  • A. last-wins(与 i18n 一致)—— 最省事,但 api / server安全配置,后一个包静默覆盖前一个包的限流预算是危险的默认。
  • B. 深合并 + 冲突报错 —— 两个栈都声明 server.security.rateLimit 且值不同 → composeStacks 抛错,和 objectConflict: 'error' 的既有姿态一致。
  • C. 显式策略参数(ComposeStacksOptions 加一项,默认 B) —— 最贵,但把选择权交给作者。

另有一个与语义无关、无论选哪个都该做的:合成时丢弃任何未处理的顶层键,应该至少 warn 一次并点名。今天它是完全静默的,这才是真正让人查不出来的部分 —— 定了语义之后,凡是新增顶层键忘了接进 composeStacks 的,也会立刻自曝,而不是等下一次有人做 #4910 这样的活儿时偶然撞见。

关联:#4910(引入 server: 的单)、#4686(三份 RateLimitConfig 零 reader)、Prime Directive #10 / #12

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions