Skip to content

refactor(spec)!: 双源清账 C9 — connector 侧 RateLimitConfig 改名 + ratchet 学会承接 def 改名 (#4684) - #4695

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4684-ratelimitconfig-dual-source
Aug 2, 2026
Merged

refactor(spec)!: 双源清账 C9 — connector 侧 RateLimitConfig 改名 + ratchet 学会承接 def 改名 (#4684)#4695
os-zhuang merged 1 commit into
mainfrom
claude/issue-4684-ratelimitconfig-dual-source

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #4684

按维护者裁决(#4684#issuecomment-5159693034)走路线 A:改 connector 侧的名,./shared 侧原样不动;门禁死结用声明式 RENAMED_DEFS 承接表解开,而不是绕过。

dual-source-exports.baseline.json:18 → 16,其余 16 行一字未动。


一、为什么是改名而不是收敛

两侧不是「同一概念两种拼法」,是两个方向相反的概念:

./shared(不动) ./integration(改名)
限的是谁 入站 —— 别人调我们的 API 出站 —— 我们调外部系统
作者写在 apis[].rateLimithttpServer.security.rateLimit connectors[].rateLimitConfig
时间窗 windowMs(毫秒),default 60000 windowSeconds(秒),必填,min 1
配额 maxRequests,default 100 maxRequests,必填,min 1
其余 enabled(default false) strategyburstCapacityrespectUpstreamLimitsrateLimitHeaders

respectUpstreamLimits / rateLimitHeaders 讲的是「读上游返回的 429 响应头」—— 入站时我们就是上游,这两个键在 apis[].rateLimit 上结构性无意义。两侧 schema 都不是 .strict(),所以把一侧的写法贴到另一侧会干净解析 + 静默丢键:

RateLimitConfigSchema.parse({ windowSeconds: 60, strategy: 'token_bucket' })
  => { enabled: false, windowMs: 60000, maxRequests: 100 }

这正是 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 名,不是任何可作者化的键。

key(旧位置) 新位置 语义/校验
integration/RateLimitConfig:strategy integration/ConnectorRateLimitConfig:strategy 不变(default token_bucket)
integration/RateLimitConfig:maxRequests integration/ConnectorRateLimitConfig:maxRequests 不变(必填 min 1)
integration/RateLimitConfig:windowSeconds integration/ConnectorRateLimitConfig:windowSeconds 不变(必填 min 1)
integration/RateLimitConfig:burstCapacity integration/ConnectorRateLimitConfig:burstCapacity 不变(可选 min 1)
integration/RateLimitConfig:respectUpstreamLimits integration/ConnectorRateLimitConfig:respectUpstreamLimits 不变(default true)
integration/RateLimitConfig:rateLimitHeaders integration/ConnectorRateLimitConfig:rateLimitHeaders 不变(三个 header 名及其 default)

authorable-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:

export const RENAMED_DEFS: Readonly< Record< string, string > > = {
  'integration/RateLimitConfig': 'integration/ConnectorRateLimitConfig',
};

三条不变式,任一违反即红:

  1. 旧 def 名下的每一个 key 都必须在新 def 名下存在(否则是「披着改名外衣的删除」);
  2. 承接表的 target 必须被本次 build 产出(拼错 / 新 def 后来被删);
  3. 承接表的 source 必须不再被产出 —— 两个都在就是复制而非改名,而复制正是这张表绝不能洗白的 dual-source 形状。

另外,承接过来的 key 保留旧 key 的 retired 状态,所以「改名顺手悄悄 retire 一个 key」仍会撞上原有的检查 (b)(必须有已登记的 D2 conversion)。这比现状更严格:#4650 之前「基线可手编」的做法可以无声删掉任意一行,承接表连一行都删不掉。

四、Sabotage 验证(全部实测,非空转证明)

4.1 承接表 —— 故意从新 def 删掉 burstCapacity

❌ 1 authorable key(s) were lost by a declared def rename:
     - integration/RateLimitConfig:burstCapacity  →  integration/ConnectorRateLimitConfig:burstCapacity  (absent)

   RENAMED_DEFS (scripts/lib/renamed-defs.ts) declares that these defs were renamed,
   and a rename must carry EVERY key: the author-facing contract is unchanged, only
   an internal schema name moved. A key missing under the new name is a real removal
   wearing a rename's clothes — and these schemas are NOT .strict(), so Zod would
   silently strip whatever the author kept writing (#3733, ADR-0104).
 ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  @objectstack/spec@17.0.0-rc.1 gen:schema  Exit status 1

4.2 承接表 —— 旧名留一个别名导出(复制而非改名)

❌ 1 problem(s) in RENAMED_DEFS (scripts/lib/renamed-defs.ts):
     - integration/RateLimitConfig → integration/ConnectorRateLimitConfig: the SOURCE def is
       still emitted by this build. That is a copy, not a rename — and a copy is precisely
       the dual-source shape this table must never be able to launder (#4411, #4446).
 ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  Exit status 1

4.3 承接表 —— target 拼错

❌ 1 problem(s) in RENAMED_DEFS (scripts/lib/renamed-defs.ts):
     - integration/RateLimitConfig → integration/ConectorRateLimitConfig: the TARGET def is
       not emitted by this build. Either the new name is misspelled here, or the renamed
       schema was since deleted — in which case its keys really did leave the contract and
       need the tombstone route, not this table.
 ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  Exit status 1

4.4 回归 pin —— 只加回一个 TYPE 别名(运行时完全看不见)

#4642 已证本包编译期 pin 空转(tsconfig.json 排除 **/*.test.ts、vitest 不开 typecheck),所以 pin 抄 PR #4689ui/view.test.ts 里那条 TypeScript compiler API 符号身份解析的形态(含防空转守卫 expect(moduleSym).toBeTruthy(),并加了第二道 origins.size > 20)。sabotage:在 connector.zod.ts 加回 export type RateLimitConfig = z.infer< typeof ConnectorRateLimitConfigSchema >;

AssertionError: expected 'src/integration/connector.zod.ts:350' to be undefined
 ❯ src/integration/connector.test.ts:780
    780|     expect(integration.get('RateLimitConfig')).toBeUndefined();
 Tests  1 failed | 41 passed (42)

其余 41 条(全部是运行时断言)全绿 —— 这正是这条编译器 API 断言不可替代的证明。

4.5 回归 pin —— 通用不变式抓一个全新的双源名

sabotage:export type CorsConfig = z.infer< typeof ConnectorRateLimitConfigSchema >;(./shared 也导出 CorsConfig)

AssertionError: expected [ 'CorsConfig', 'FieldMapping', …(1) ] to deeply equal [ Array(2) ]
+   "CorsConfig",
    "FieldMapping",
    "FieldMappingSchema",

(FieldMapping / FieldMappingSchema 是本对入口尚存的已知双源 —— C12 的单子,显式列在 pin 里,所以新出现一个就会红,而不是写成一条从来不成立的「不许同名」。)

以上 sabotage 全部已回滚,工作树干净。

五、门禁实际输出

✓ All 8 generated artifacts are up to date.           (check:generated)
✅  self-test: flags same-name different-declaration exports, and nothing else.
✅  no new dual-source exports: 4325 names across 16 entry points
    — 166 re-exported (single declaration), 16 accepted dual-source (baseline).   ← 18 → 16
✓ all governed-type properties are classified …       (check:liveness)
✓ strictness ledger: 67 file(s) across 5 triaged director(ies) …
✓ all classified (1 closed, 2 open, 4 output, 9 scope) (check:empty-state)
✓ variant/doc gate: 20 discriminated union(s) — 8 governed, 12 exempt
✅  no exported type resolves to `any`: 1893 types + 1639 schemas
✅ 202 prose examples type-check against @objectstack/spec
check-adr-anchors: OK (17 anchored file(s))

vitest (packages/spec):  Test Files 293 passed (293) | Tests 7367 passed (7367)
pnpm typecheck (全仓):   Tasks 122 successful, 122 total

未动 zod 形状的严格性,docs/audits/2026-07-unknown-key-strictness-ledger.md 无需同步(check:strictness-ledger 绿,该文件本来也不含 RateLimit)。

六、顺带产物

content/docs/references/integration/http.mdxgen:docs 删除,meta.json"http" 条目自动移除。那个页面本身就是本 bug 的产物:build-docs.tsschemaZodFileMap名字全局索引,shared/http.zod.ts 的同名声明覆盖了 connector 的,于是 connector 的 RateLimitConfig 被塞进了一个叫 integration/http 的页面。改名后它归位到 integration/connector.mdx

七、changeset

@objectstack/spec major(.changeset/rate-limit-config-dual-source-c9.md)。理由据实:def 改名对按名字 import 该 type 的 TS 代码是真破坏,必须改 import;但对作者写的元数据零影响,所以 changeset 里明确写了「无需迁移元数据」、FROM → TO 的 import 改法、以及 $id 的迁移。与 C11 的 patch 情形不同(C11 是 FROM ≡ TO 零消费者影响),没有照抄。

八、明确没做的事

https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9


Generated by Claude Code

`@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
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 2, 2026 6:55pm

Request Review

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec 双源清账 C9:RateLimitConfig / RateLimitConfigSchema(./integration ≠ ./shared)—— 2 条

2 participants