refactor(spec)!: 双源清账 C9 — connector 侧 RateLimitConfig 改名 + ratchet 学会承接 def 改名 (#4684) - #4695
Merged
Merged
Conversation
`@objectstack/spec` exported `RateLimitConfig` / `RateLimitConfigSchema` from two entry points for two DIFFERENT declarations — `./shared` limits INBOUND API traffic (`enabled` / `windowMs` / `maxRequests`, all defaulted), `./integration` throttles OUTBOUND connector calls (`strategy` / `maxRequests` / `windowSeconds` required, plus the upstream `X-RateLimit-*` header names). Neither schema is `.strict()`, so a snippet copied between the two parsed clean with its foreign keys silently stripped (#4411 trap / ADR-0104 silent-strip class). They are two concepts, not two spellings, so ADR-0112 D9(a) applies — the same ruling that produced `ConnectorErrorCategory` and `ConnectorRetryStrategy` ten lines below. The connector side is renamed to `ConnectorRateLimitConfig`; `./shared` keeps its name, keys and defaults. No back-compat alias: it would be a third declaration of the name this change is removing. Zero authorable-key change — all six keys under `connectors[].rateLimitConfig` parse exactly as before — hence no ADR-0087 conversion and no tombstone. Riding along: `scripts/build-schemas.ts` learns a declarative `RENAMED_DEFS` table (`scripts/lib/renamed-defs.ts`). Its two ratchets measure in `$def` units, so a def rename previously read as six authorable keys vanishing at once, with all three suggested remedies wrong for a rename (hand-editing the surface is banned by #4650; a tombstone + conversion would register a migration nobody must run). The table enforces the rule a rename must obey — every key under the old def must exist under the new one, the target must be emitted, and the source must not (a def still published is a copy, not a rename) — which is strictly stronger than the hand-edited baseline it replaces. dual-source-exports baseline: 18 -> 16. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 2, 2026 18:57
os-zhuang
enabled auto-merge
August 2, 2026 18:57
This was referenced Aug 2, 2026
Closed
app.areas[] 的 visible / requiredPermissions 是 fail-open 的访问闸门 —— 服务端从不走 areas(ADR-0049,v17 限时)
#4651
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4684
按维护者裁决(#4684 的
#issuecomment-5159693034)走路线 A:改 connector 侧的名,./shared侧原样不动;门禁死结用声明式RENAMED_DEFS承接表解开,而不是绕过。dual-source-exports.baseline.json:18 → 16,其余 16 行一字未动。一、为什么是改名而不是收敛
两侧不是「同一概念两种拼法」,是两个方向相反的概念:
./shared(不动)./integration(改名)apis[].rateLimit、httpServer.security.rateLimitconnectors[].rateLimitConfigwindowMs(毫秒),default 60000windowSeconds(秒),必填,min 1maxRequests,default 100maxRequests,必填,min 1enabled(defaultfalse)strategy、burstCapacity、respectUpstreamLimits、rateLimitHeadersrespectUpstreamLimits/rateLimitHeaders讲的是「读上游返回的 429 响应头」—— 入站时我们就是上游,这两个键在apis[].rateLimit上结构性无意义。两侧 schema 都不是.strict(),所以把一侧的写法贴到另一侧会干净解析 + 静默丢键:这正是 ADR-0104 的沉默剥离类。ADR-0112 D9(a) 对同一对文件、同一类冲突已有裁决(产物
ConnectorErrorCategory/ConnectorRetryStrategy就在本次改名的 schema 下方十行):改 connector 侧的名,so one name means one thing。ConnectorRateLimitConfigSchema/ConnectorRateLimitConfig。不留兼容别名 —— 别名会是同名的第三个声明,等于把本 PR 关掉的陷阱重新打开。二、6 个 authorable key 核对表 —— 一个不少
作者面零变化:改的是 TS 导出名与内部 JSON Schema
$def名,不是任何可作者化的键。integration/RateLimitConfig:strategyintegration/ConnectorRateLimitConfig:strategytoken_bucket)integration/RateLimitConfig:maxRequestsintegration/ConnectorRateLimitConfig:maxRequestsintegration/RateLimitConfig:windowSecondsintegration/ConnectorRateLimitConfig:windowSecondsintegration/RateLimitConfig:burstCapacityintegration/ConnectorRateLimitConfig:burstCapacityintegration/RateLimitConfig:respectUpstreamLimitsintegration/ConnectorRateLimitConfig:respectUpstreamLimitstrue)integration/RateLimitConfig:rateLimitHeadersintegration/ConnectorRateLimitConfig:rateLimitHeadersauthorable-surface.json的实际 diff 是 6 增 6 删、逐条一一对应(-6/+6,无第七行)。零 tombstone、零 ADR-0087 conversion —— 本簇没有任何 key 被 retire,check:spec-changes/check:upgrade-guide均无 diff。三、
RENAMED_DEFS承接表(本簇真正的工作量)packages/spec/scripts/build-schemas.ts的两道 ratchet 都以 def 为计量单位:json-schema.manifest.json—— 发布过的每个 schema;authorable-surface.json—— 每个def:prop作者可写键。def 改名在它们眼里等同于删除。而三条既有补救全部堵死:手编 surface 被 #4650 明令禁止;
retiredKey()+ D2 conversion 语义错误(没有 key 被 retire,墓碑没有活着的 def 可挂,conversion 的 surface 全是作者路径而作者路径根本没变,伪造会污染 ADR-0087 登记册 —— #4659 那类「门禁绿但登记错」);删 manifest 行则对下面的 6 个 key 只字未提。所以让门禁学会改名。新增
packages/spec/scripts/lib/renamed-defs.ts:三条不变式,任一违反即红:
另外,承接过来的 key 保留旧 key 的 retired 状态,所以「改名顺手悄悄 retire 一个 key」仍会撞上原有的检查 (b)(必须有已登记的 D2 conversion)。这比现状更严格:#4650 之前「基线可手编」的做法可以无声删掉任意一行,承接表连一行都删不掉。
四、Sabotage 验证(全部实测,非空转证明)
4.1 承接表 —— 故意从新 def 删掉
burstCapacity4.2 承接表 —— 旧名留一个别名导出(复制而非改名)
4.3 承接表 —— target 拼错
4.4 回归 pin —— 只加回一个 TYPE 别名(运行时完全看不见)
#4642 已证本包编译期 pin 空转(
tsconfig.json排除**/*.test.ts、vitest 不开typecheck),所以 pin 抄 PR #4689 在ui/view.test.ts里那条 TypeScript compiler API 符号身份解析的形态(含防空转守卫expect(moduleSym).toBeTruthy(),并加了第二道origins.size > 20)。sabotage:在connector.zod.ts加回export type RateLimitConfig = z.infer< typeof ConnectorRateLimitConfigSchema >;其余 41 条(全部是运行时断言)全绿 —— 这正是这条编译器 API 断言不可替代的证明。
4.5 回归 pin —— 通用不变式抓一个全新的双源名
sabotage:
export type CorsConfig = z.infer< typeof ConnectorRateLimitConfigSchema >;(./shared也导出CorsConfig)(
FieldMapping/FieldMappingSchema是本对入口尚存的已知双源 —— C12 的单子,显式列在 pin 里,所以新出现一个就会红,而不是写成一条从来不成立的「不许同名」。)以上 sabotage 全部已回滚,工作树干净。
五、门禁实际输出
未动 zod 形状的严格性,
docs/audits/2026-07-unknown-key-strictness-ledger.md无需同步(check:strictness-ledger绿,该文件本来也不含RateLimit)。六、顺带产物
content/docs/references/integration/http.mdx被gen:docs删除,meta.json的"http"条目自动移除。那个页面本身就是本 bug 的产物:build-docs.ts的schemaZodFileMap按名字全局索引,shared/http.zod.ts的同名声明覆盖了 connector 的,于是 connector 的RateLimitConfig被塞进了一个叫integration/http的页面。改名后它归位到integration/connector.mdx。七、changeset
@objectstack/specmajor(.changeset/rate-limit-config-dual-source-c9.md)。理由据实:def 改名对按名字 import 该 type 的 TS 代码是真破坏,必须改 import;但对作者写的元数据零影响,所以 changeset 里明确写了「无需迁移元数据」、FROM → TO 的 import 改法、以及$id的迁移。与 C11 的 patch 情形不同(C11 是 FROM ≡ TO 零消费者影响),没有照抄。八、明确没做的事
RateLimitStrategy/RateLimitStrategySchema(同文件的枚举)。它不是双源(仅./integration导出),改它对基线零贡献,属于范围外。content/docs/releases/。packages/spec/authorable-surface.json(authorable-surface 的 tombstone 门禁可被手编基线绕过 —— 删掉基线行就删掉了证据(#4638 / #4643 已两次这样过绿) #4650)。RateLimitConfig全仓零 runtime reader,真正限流的是packages/runtime/src/security/rate-limit.ts里第三份形状)—— 那是独立的执行面决定,本 PR 落地不使其失效。https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
Generated by Claude Code