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。

Activity

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions