Repository navigation
composeStacks silently drops every non-array top-level key — api: today, server: as of #4910 #5005
Copy link
Copy link
Closed
Labels
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Aug 3, 2026 xuyushun441-sys commented
on Aug 4, 2026 CollaboratorAuthorMore actions维护者裁决(2026-08-04):冲突即报错 + 未处理键必警
- 组合语义:同值放行;冲突报错,错误信息带处方(点名冲突键与两个来源栈,指示显式指定保留哪个或改一致)。⛔ 不做 last-wins(静默安全降级——先声明 403 门的栈被后组合者无声覆盖,正是本单
api:被丢的孪生形态)、不做 deep-merge(制造新的静默歧义类)。 - 未处理顶层键必须 warn —— 恢复不变量部分,实现时与报错语义同 PR 落(下一个新顶层键自己报告,不再靠 「v17」入站 rateLimit 接执行:ApiEndpoint / HttpServer 的 RateLimitConfig 推导为 runtime token bucket 配置,dispatcher 生效(#4686 拆向之一) #4910 式偶然发现)。
- 显式覆盖机制(客户定制 vendor 包场景)留给定制故事真拉动时设计,本单不预支。
- 业务定性:组合栈是应用打包/安装的商业承载,静默丢键是信任级 bug——修复优先级列本轮派发队列首位。
needs-user-decision摘除,入队。
Generated by Claude Code
- 组合语义:同值放行;冲突报错,错误信息带处方(点名冲突键与两个来源栈,指示显式指定保留哪个或改一致)。⛔ 不做 last-wins(静默安全降级——先声明 403 门的栈被后组合者无声覆盖,正是本单
xuyushun441-sys commented
on Aug 4, 2026 CollaboratorAuthorMore actions🔒 认领:PM 循环派发(批 15 验收后空位,裁决执行队列首位——活 bug)
会话:session_01Ehu85kbvMcrNTUJjwxvLJ9
分支:claude/issue-5005-compose-stacks-merge-semantics
Worktree:objectstack-issue-5005
域:domain:engine
文件面:composeStacks实现及其测试(objectql/runtime,按实测定位)、changeset。⛔ 不碰 packages/spec 源(若发现需要 spec 侧配合的新契约问题,needs_decision 不猜)。按 2026-08-04 裁决执行:同值放行、冲突报错带处方、未处理顶层键必警;⛔ 不做 last-wins / deep-merge;显式覆盖机制不预支。与在飞批 14(spec/ui)、#4956(spec scripts)文件面不相交。
Generated by Claude Code
xuyushun441-sys commented
on Aug 4, 2026 CollaboratorAuthorMore actions✅ 验收通过 —— PR #5053(已挂轨)
复核实况(3 文件;
stack.zod.ts零 zod schema 定义变更实查;authorable/api-surface 零漂移;⛔ releases/ 零触碰):- 位置修正接受:
composeStacks实住packages/spec/src/stack.zod.ts:1413而非派发词假设的引擎侧。dev 编辑 spec 源但保持契约字节级中性并显著申明偏差——fix(spec): make the bulk-action option item's openness deliberate, not accidental (#4001) #4909 诚实偏差先例,比空转一轮 needs_decision 正确。 - 修法超裁决要求:「未处理键必警」升级为编译期不通过——disposition 表
Record<keyof ObjectStackDefinition, ComposeDisposition>全覆盖,新顶层键不声明组合语义即 TS2741(删表项实证)。CONCAT_ARRAY_FIELDS由表推导,手工白名单退役。 - 爆炸半径实测 3 → 11 键:
functions被丢意味着组合栈的全部声明式 handler 启动静默失败——比立单点名的限流丢失更重;七个声明数组集合(datasets/jobs/emailTemplates/docs/books/tiers/datasourceMapping)同样在漏。ADR-0109 曾按键打补丁(tools)正是白名单模式的病历。 - RED-first 复现立单证据(api/server 丢失断言先红,16 红 6 绿 → 实现后 66 全绿);既有 44 例组合测试零改动全绿;
functions按名合并、重名报错、map/array 各保形态不互转。 - 衍生
composeStacks的i18n仍是 last-wins —— #5005 裁决否掉的那个形状,只剩这一个键还在用 #5051(i18n last-wins 是仅存不一致键)已标 v18 地图——翻译叠加可能正是设计意图,无活伤害不进急件区。
Generated by Claude Code
- 位置修正接受:
- added a commit that references this issue
on Sep 28, 2026
发现于 #4910 开发过程(入站 rateLimit seam),与本单修复无关,按 Prime Directive #10 单独立单,未认领。
事实
composeStacks(packages/spec/src/stack.zod.ts)只合并三类东西:manifest—— 按manifest策略择一;i18n—— last-wins;objects+CONCAT_ARRAY_FIELDS里列出的数组集合 —— 拼接。其余一律不进
composed。它从一个空对象{}开始逐项填充,所以任何既不是manifest/i18n、也不在数组清单里的顶层键,合成后直接消失,没有告警。今天受影响的顶层键:
api(enableProjectScoping/projectResolution/enforceProjectMembership)objectstack serve(serve.ts),转发给 REST + dispatcherserver(security.rateLimit/trustProxy,#4910 新增)objectstack serve→ dispatcher 的入站限流器datasourceMappingdatasourceMapping尤其值得一看:它是数组,只是没被列进CONCAT_ARRAY_FIELDS(需实测确认)。为什么这是 declared ≠ enforced 的形状
作者写下
server.security.rateLimit,单栈下真限流;把同一个栈丢进composeStacks([base, addon]),限流静默消失,defineStack不报错、validate不报错、启动日志也不会说少了什么 —— 因为消费方看到的就是undefined,与「没写过」无法区分。这正是 #4686 那一类缺陷换了个入口。api.enforceProjectMembership走同一条路径,后果是每环境成员 403 闸门在合成栈里悄悄关掉,安全影响比限流更直接。复现(未跑,依据是源码 —— 请开工时先实测确认)
注意
composeStacks的短路:stacks.length === 1时原样返回,所以单元素合成看起来是好的,只有真正 ≥2 个栈才丢 —— 这也解释了为什么至今没被发现。待裁决(不要猜)
顶层标量/对象键的合成语义没有先例可循,需要维护者定一次,而不是每个键各定各的:
i18n一致)—— 最省事,但api/server是安全配置,后一个包静默覆盖前一个包的限流预算是危险的默认。server.security.rateLimit且值不同 →composeStacks抛错,和objectConflict: 'error'的既有姿态一致。ComposeStacksOptions加一项,默认 B) —— 最贵,但把选择权交给作者。另有一个与语义无关、无论选哪个都该做的:合成时丢弃任何未处理的顶层键,应该至少 warn 一次并点名。今天它是完全静默的,这才是真正让人查不出来的部分 —— 定了语义之后,凡是新增顶层键忘了接进
composeStacks的,也会立刻自曝,而不是等下一次有人做 #4910 这样的活儿时偶然撞见。关联:#4910(引入
server:的单)、#4686(三份 RateLimitConfig 零 reader)、Prime Directive #10 / #12。