Skip to content

feat(spec)!: 退休 datasource.readReplicas —— 声明了、strict 了、刚被加了校验,但没有任何东西打开过副本连接 (#4468) - #4481

Merged
os-zhuang merged 2 commits into
mainfrom
claude/datasource-config-validation-i7wdmo
Aug 1, 2026
Merged

feat(spec)!: 退休 datasource.readReplicas —— 声明了、strict 了、刚被加了校验,但没有任何东西打开过副本连接 (#4468)#4481
os-zhuang merged 2 commits into
mainfrom
claude/datasource-config-validation-i7wdmo

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #4468

结论

datasource.readReplicas 描述的副本连接,从来没有任何东西打开过

  • ConnectableDatasource / DatasourceConnectionSpec 没有 replicas 字段 —— 配置到不了驱动层
  • createDefaultDatasourceDriverFactory 从不读这个 key
  • 没有任何查询路径区分读和写

第三条是决定性的。就算副本连接建好了,也没有东西可以路由过去 —— 平台不存在读写分离。所以每一条语句一直都走主库,无论这里声明了什么。

按 ADR-0049 enforce-or-remove 走 remove,因为 enforce 的对象不存在:这不是一个"差最后一根线"的字段,它描述的整个机制都还没建。

为什么这个字段特别值得写一段

#4410(上一个 PR,#4465)修的是 datasource.config 从来没人校验。修的过程中很自然地把新的按驱动校验也铺到了 readReplicas 的每一条上 —— 副本和主库配置同形,这个决定当时是对的。

结果是这个 slot 集齐了一个"能用的功能"的全部外观特征:

  • 有声明,有文档注释
  • 顶层 .strict() 守着,拼错 key 会被拒
  • 每一条副本配置逐字段按驱动契约校验,hostname 写错会按下标报出来:"第 1 条副本的 hostname 应该是 host"

这些没有一条能证明存在消费者,而每一条读起来都像。给一个死 slot 加精度是廉价的,把它和活的区分开是昂贵的 —— 这正是 ADR-0049 要的是 consumer 而不是 rigor 的原因。

第二个被骗到的表面在 objectui:DatasourcePreview 渲染一枚 "2 read replicas" 的 pill。作者配好副本、保存、看到 pill 亮起,得到的是平台在说"我收到了"。它还是两个仓库里唯一承认这个 key 存在的地方,所以它就是"这功能能用"的全部证据。packages/spec/liveness/README.md 有一条常设规则专治这个 —— authoring/preview renderer 永远不算 runtime consumer,2026-06 那次仅凭 preview 定性的扫描,13 个属性复核后错了 10 个。

配套 PR:objectstack-ai/objectui#(见下方 objectui 分支)。两边一起走,避免出现 spec 已经拒收、UI 还在数副本数量的中间态。

改了什么

.claude/skills/spec-property-retirement 的 strict-removal 路线(DatasourceSchema.strict(),所以是删 key + guidance map,不是 retiredKey() tombstone):

改动
Schema key 从 shape 和 DATASOURCE_KEYS 删除;replicas 别名改为共享同一条 prescription(而不是重命名到一个同样已消失的 key);#4410 加的 readReplicas 校验循环一并移除
Conversion datasource-read-replicas-removed(D2)+ step-17 chain 接线,retiredFromLoadPath: true —— 和其他"自我描述失实"而非"被改名"的 key 同档
基线 authorable-surface.json 的行有意删除 —— 这是 gate (a) 对 strict removal 的绊线,skill 明确要求在同一个 PR 里删掉;spec-changes.json / upgrade guide / reference docs 重新生成
测试 pin test 从"逐条校验并按下标定位"翻转为"整个 slot 被拒,且拒绝信息带处方"。测试里故意用一条格式完全正确的副本配置,把"拒的是 key 存在本身,不是里面有错"钉住
文档 v17 release notes(dead clusters 表 + 升级清单)、strictness ledger 两行
Changeset 新增 major;另外修正了两个尚未发布的 changeset 里仍在描述这个 key 的段落

顺手修正的一处旧文案

content/docs/releases/v17.mdx 里 datasource 那段仍然写着"Nothing validates inside them … tracked as #4410"。#4410 已经在 #4465 里落地了,这句话在合并那一刻就过期了 —— 是我上一个 PR 漏掉的。这次一并改成描述现状。

迁移

没有可迁移的目标,因为没有读副本路由可以迁移过去。

写了 改成
readReplicas: [{ host: 'replica-a', … }] 删掉
replicas: [ … ] 删掉

os migrate meta --from 16 自动删除,每个 datasource 一条 notice。手写会被 parse 拒绝并给出处方。

读吞吐不受影响 —— 那些连接本来就没建立过。真需要副本读,把它们藏在单个端点后面(pgpool、ProxySQL、RDS reader endpoint),config 指过去。这在这个 key 被删之前也是唯一有效的路径。

验证

check:liveness               PASS
check:empty-state            PASS
check:authorable-surface     PASS
check:docs                   PASS
check:api-surface            PASS
check:spec-changes           PASS
check:upgrade-guide          PASS
check:skill-refs             PASS
check:skill-docs             PASS
check:skill-examples         PASS
check:strictness-ledger      PASS
  • @objectstack/spec — 286 files / 7253 tests passed
  • @objectstack/cli — 66 files / 678 tests passed(含 migrate-meta.e2e,覆盖 conversion 重放与幂等)
  • @objectstack/service-datasource — 11 files / 195 tests passed
  • objectui 侧:eslint clean(确认没有残留未使用的 import)

datasource 不在 liveness ledger 的 GOVERNED 列表里,所以没有 ledger 行要动。这本身是个缺口 —— datasource 是注册的 metadata type,却不受活性闸门管辖,这正是这个 key 能一直躺着的结构性原因。没有并进本 PR,因为那是一次 ledger 扩容而不是一次 retirement。

后续(已开)


Generated by Claude Code

claude added 2 commits August 1, 2026 09:00
Several agents work this repo at once and an unassigned issue reads as an
open invitation. Two agents starting the same issue burn the same hours
twice and then race to land conflicting shapes for one problem.

Makes assignment the first action of a task — before the worktree, before
the first read — and states the corollary for Prime Directive #10: file a
finding unassigned, assign it at the moment you actually start, so the
issue list works as a queue other agents can trust.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsgTqRF58HsQYKLsrZ5pQY
BREAKING CHANGE: `datasource.readReplicas` is removed.

It described replica connections nothing ever opened. `ConnectableDatasource`
and `DatasourceConnectionSpec` carry no replicas field, the driver factory
never reads the key, and no query path distinguishes a read from a write —
the platform has no read/write splitting at all, so every statement always
went to the primary regardless of what was declared.

There is no target to move to, because there is no read-replica routing to
move to. Front replicas behind one endpoint (pgpool, ProxySQL, an RDS reader
endpoint) and point `config` there; `os migrate meta --from 16` strips the key.

Worth recording why this one survived so long: #4410 closed the
`datasource.config` gap and, reasonably, extended the new per-driver
validation over each `readReplicas` entry. The result was a slot with every
marker of a working feature — declared, `.strict()`-guarded, and validated
field-by-field against the driver's contract, rejecting a misspelt replica
host by index. None of that is evidence of a consumer and all of it reads
like one. Rigor is cheap to add to a dead slot and expensive to tell apart
from life, which is why ADR-0049 asks for a consumer rather than for rigor.

The retirement kit:

- DatasourceSchema: key deleted (strict route), `readReplicas`/`replicas`
  added to the guidance map so the rejection carries the prescription; the
  #4410 validation loop removed with it
- `datasource-read-replicas-removed` D2 conversion + step-17 chain wiring,
  retired from the load path like the other keys retired for misdescribing
  themselves
- authorable-surface baseline line deleted deliberately (gate (a)'s strict-
  removal trip wire); spec-changes, upgrade guide, reference docs regenerated
- pin test flipped from "validates each entry" to "rejects the slot"
- release notes, strictness ledger, and the two pending changesets that still
  described the key corrected

Follow-ups filed: #4479 (read-replica routing as a real feature request,
starting from the read/write decision point) and #4480 (the same feature
declared a second time as `DatabaseConnector.readReplicaConfig`, also unread).

Closes #4468

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsgTqRF58HsQYKLsrZ5pQY
@vercel

vercel Bot commented Aug 1, 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 1, 2026 9:24am

Request Review

@github-actions

github-actions Bot commented Aug 1, 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.

@os-zhuang
os-zhuang merged commit 9ca2d85 into main Aug 1, 2026
18 checks passed
@os-zhuang
os-zhuang deleted the claude/datasource-config-validation-i7wdmo branch August 1, 2026 09:38
os-zhuang added a commit to objectstack-ai/objectui that referenced this pull request Aug 1, 2026
`datasource.readReplicas` is retired in @objectstack/spec 17 — nothing in the
platform ever opened a replica connection, so the "2 read replicas" pill
reported a configuration that did not exist.

The pill did not echo the draft back, which is what a preview panel is for and
is harmless. It concluded. And it was the only surface in either repo that
acknowledged the key at all, so it was the whole of the evidence that the
feature worked.

Companion to objectstack-ai/objectstack#4481.
os-zhuang added a commit that referenced this pull request Aug 1, 2026
…ype (#4487) (#4489)

`GOVERNED` was a hand-maintained list and nothing compared it against the
registry it claims to cover. It governed 15 of 25 registered metadata types
while reporting itself complete: a type in the other ten was authorable —
served by /api/v1/meta/types/:type, editable in Studio — and was never asked
who reads its properties, so an inert key on it was invisible to CI and its
silence read as success.

`datasource` was in that state for its entire life. #4410, #4465 and #4481
found six inert keys on it by hand, two security-shaped: `schemaMode` was
dropped between record and connection spec, so a database ObjectStack must
never run DDL against was constructed as `managed`; `ssl` stopped at the
record, so a TLS block with a CA certificate configured nothing while looking
identical to one that worked.

The gate is now answerable to the registry. Every registered type must be in
GOVERNED or in PENDING_GOVERNANCE with a reason and an issue; registering a
type and forgetting the ledger fails CI with the entry to write. The reverse
rots too and also fails — a PENDING_GOVERNANCE row for a type since governed
claims a debt that no longer exists.

`datasource` is now governed: 43 properties classified with evidence, and the
result is the highest dead ratio of any governed type — 20 of 43 have no
runtime consumer. `capabilities.*` (11): the engine gates pushdown on the
runtime driver's own `supports.*` object, a non-overlapping vocabulary.
`healthCheck.*` (3): nothing schedules a datasource probe. `retryPolicy.*`
(4): no connect or query path retries. Plus `external.label` and
`external.requirePermission`.

One correction ships with this, and it is why the audit was worth doing.
`capabilities.readOnly` reads as a safety switch and gates nothing — and two
shipped prescriptions pointed authors at it: the externalSettingsUnknownKeyError
guidance in datasource.zod.ts and the #4465 changeset's relocation table. Both
now name `external.allowWrites: false`, the write gate the engine checks. The
v17 release notes carried a matching false claim about `capabilities` gating
pushdown; corrected here too.

The CLI advisory lint picks the ledger up, so `os compile` warns an author who
sets any of the 20. That needed `datasource` in TYPE_COLLECTIONS: coverage grows
by marking entries authorWarn only WITHIN a type the lint already walks, and a
governed type whose collection is unregistered has a correct ledger that warns
nobody.

Nine types remain ungoverned and are now enumerated rather than implied (#4488).


Claude-Session: https://claude.ai/code/session_01WsgTqRF58HsQYKLsrZ5pQY

Co-authored-by: Claude <noreply@anthropic.com>
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 protocol:data size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

datasource.readReplicas 声明了、strict 了、刚被加了校验——但没有任何消费者(ADR-0049 enforce-or-remove,#4410 收尾发现)

2 participants