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.rateLimitconnectors[].rateLimitConfig
时间窗windowMs(毫秒),default 60000windowSeconds(秒),必填,min 1
配额maxRequests,default 100maxRequests,必填,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:strategyintegration/ConnectorRateLimitConfig:strategy不变(default token_bucket)
integration/RateLimitConfig:maxRequestsintegration/ConnectorRateLimitConfig:maxRequests不变(必填 min 1)
integration/RateLimitConfig:windowSecondsintegration/ConnectorRateLimitConfig:windowSeconds不变(必填 min 1)
integration/RateLimitConfig:burstCapacityintegration/ConnectorRateLimitConfig:burstCapacity不变(可选 min 1)
integration/RateLimitConfig:respectUpstreamLimitsintegration/ConnectorRateLimitConfig:respectUpstreamLimits不变(default true)
integration/RateLimitConfig:rateLimitHeadersintegration/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:

exportconstRENAMED_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/specmajor(.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

vercelBot commented Aug 2, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 2, 2026 6:55pm

Request Review

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

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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@os-zhuang@claude