Skip to content

fix: 四处静默失真 — datasource 映射、meta type 归一化、value-shape 扫描、未接线的 lint 规则 (#4462, #4432, #4455, #4449) - #4520

Merged
os-zhuang merged 4 commits into
mainfrom
claude/v17-verification-defects-gnf9e6-datasource
Aug 1, 2026
Merged

fix: 四处静默失真 — datasource 映射、meta type 归一化、value-shape 扫描、未接线的 lint 规则 (#4462, #4432, #4455, #4449)#4520
os-zhuang merged 4 commits into
mainfrom
claude/v17-verification-defects-gnf9e6-datasource

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

v17 验收缺陷四项:每一处都在"成功"的外表下做了错的事,所以没有任何门禁报警。

Fixes #4462
Fixes #4432
Fixes #4455
Fixes #4449

来自 v17 验收缺陷汇总 #4482。四项各自一个 commit,可分别 review。


1. #4462 — 映射到不可达 datasource 的对象,静默读写 DEFAULT 库

最严重的一项:数据落错库且无人知晓。

根因(两层,各自都是对方能被修好的前提)

第一层 —— 路由层会静默兜底。 ObjectQLEngine.getDriver 第 2 步原本写的是
mapped && this.drivers.has(mapped):一条已匹配的 mapping 规则,只要它指向的
datasource 没有活 driver,就直接落到第 5 步用 default driver。于是启动成功、/ready
200、日志里该 datasource 名出现 0 次、写入返回 201,而行落在 default 库里。

第二层 —— 启动时根本没尝试连接它。 ADR-0062 D2 的 phase-1 说明明确把"被 mapping
指向"排除在自动连接闸门之外,理由是保持 examples/app-crm 逐字节不变。那条说明只算了
天平的一侧;另一侧就是上面这个后果。datasourceMapping 在任何作者眼里都读作"路由",
不是装饰。

改动

  • 路由不再兜底@objectstack/objectql):已匹配的规则指向无 driver 的 datasource
    时抛错 —— 连接层记录过判定就抛 DatasourceUnavailableError被 connect policy 拒绝的 datasource,查询时报的错说不出原因:getDriver 只会说 "is not registered" #3828),否则抛一条点名
    对象、datasource 和两条补救路径的错误。default 仍然继续向下解析:默认 driver 保留
    自己的自然名(ADR-0062 D1 收尾:default driver 的 connect 与失败判决仍是第二份实现(阻塞点已定位) #3826),drivers.has('default') 天生为 false,第 5 步就是路由到它
    的方式。
  • D2 闸门新增 (d)service-datasource + runtime):mapping 规则至少路由了一个
    对象的 datasource 会在启动时自动连接,declared-auto 失败致命并给出可读原因 ——
    这与闸门 (b)(显式 object.datasource 绑定)是同一个判断,现在对 (d) 也成立,正是
    因为第一层去掉了兜底。OS_ALLOW_DRIVER_CONNECT_FAILURE 仍然可降级启动。
  • 被 mapping 路由的对象清单来自引擎自己的匹配器resolveMappedDatasource,新公开),
    connectDeclared({ mappedObjects }) 传入。连接服务绝不自己再实现一遍规则匹配 ——
    两个匹配器差一个子句,结果就是"连了个路由不用的库"或"路由过去但没人连",也就是这个
    缺陷本身。

关于 examples/app-crm

它的 datasourceMapping删除了,而这恰恰是让该示例行为保持不变的原因,不是破坏它:
namespace: 'crm' 规则从来没匹配上过(namespace 已废弃,没有对象设它),
default: true → crm_primary 规则把所有东西都路由到一个没连接的 :memory: datasource,
也就是靠兜底落在 default 库。真去遵守这条规则,会把整个应用(含平台对象)搬到一个每次
启动都空的内存库上。

验证证据(真实启动,专属端口 38107,非仅单测)

以下三段都是实跑输出,不是构造的:

(a) 未改动的 CRM 示例照常健康启动,两个 datasource 保持 unvalidated(仅元数据)——
即行为与修复前一致:

$ curl -s http://127.0.0.1:38107/api/v1/datasources
{"success":true,"data":{"datasources":[
  {"name":"default","driver":"sqlite","origin":"code","active":true,"status":"ok"},
  {"name":"crm_primary","driver":"sqlite","origin":"code","active":true,"status":"unvalidated"},
  {"name":"crm_analytics","driver":"sqlite","origin":"code","active":true,"status":"unvalidated"}]}}

(b) 临时把 crm_account 映射到 postgres://nobody:secret123@127.0.0.1:1/nonexistent_db
后启动,进程退出码 1,服务器不起来
(验证后已还原配置并重建产物):

ERROR [AppPlugin] declared-datasource auto-connect failed for app 'com.example.crm':
datasource 'broken': connect failed — connect ECONNREFUSED 127.0.0.1:1.
(1 object(s) are routed to it by a datasourceMapping rule (crm_account) and have no
fallback datasource — their reads/writes would otherwise land in a DIFFERENT database
than the one they declare ⇒ fail-fast per ADR-0062 D5). Fix the datasource
configuration, or set OS_ALLOW_DRIVER_CONNECT_FAILURE=1 to boot anyway and serve
errors until it is reachable.

  ✗ Plugin plugin.app.com.example.crm failed to start - rollback complete
exit=1

(c) 加 OS_ALLOW_DRIVER_CONNECT_FAILURE=1 降级启动,横幅带同一句话,且被映射对象的
seed 直接失败(而不是悄悄写进 default 库):

⚠️ DEGRADED BOOT: datasource 'broken': connect failed — connect ECONNREFUSED 127.0.0.1:1
(1 object(s) are routed to it by a datasourceMapping rule (crm_account) and have no
fallback datasource — their reads/writes would otherwise land in a DIFFERENT database
than the one they declare), but OS_ALLOW_DRIVER_CONNECT_FAILURE is set — starting anyway.
...
ERROR Insert operation failed {"object":"crm_opportunity","error":{"message":"Account is required"}}

最后这行是关键旁证:crm_account 的 seed 没能写进 default 库,所以引用它的
opportunity seed 才失败 —— 兜底确实消失了。

没有实测 /ready 由 200 转 503 的场景。本项选择的是启动失败(而非启动成功
后由 /ready 报告降级),所以正常路径下服务器根本不会起来;降级路径由 (c) 覆盖。

ADR-0062 D2 已加修订说明(明确推翻 phase-1 note 并写清取舍),文档页与 data skill 同步
改为"mapping 规则是路由,连不上就启动失败"。

取舍说明(需要 reviewer 认可的产品判断)

任务卡允许我在多个合理选项中选一个并说明。我选了 启动失败(fail-fast),而不是
"启动成功但 /ready 转 503",理由:

  1. 与 ADR-0062 D5 对显式 object.datasource 绑定的既有姿态完全一致 —— 同样是"无兜底",
    没有理由给两种写法两种结局;
  2. /ready 转 503 意味着进程活着但不可用,运维要多绕一层才能看到原因;fail-fast 的错误
    文本直接点名对象、datasource 和补救办法;
  3. 逃生舱已经存在且是共用的(OS_ALLOW_DRIVER_CONNECT_FAILURE),"我知道库连不上,先起来"
    的运维意图有现成出口,不需要新开关。

这是可观察行为变化,因此 changeset 定为 minor 而非 patch,正文里写明了升级后启动
失败该怎么办。


2. #4432 — meta overlay 的 type 段不归一化

根因

#3985/meta每类型闸门接受单复数两种拼法,但没有折叠它们。于是
/meta/actions/x/meta/action/x 指向两个命名空间,而下面每一层对"这条目在哪个空间"
的看法并不一致:SysMetadataRepository 自己折叠成单数,而它上面的授权层
isOverlayAllowed / isArtifactBacked)、下面的注册表修复(restoreArtifactRegistryView
和列表水合都读调用方的拼法。

真正造成破坏的是水合那一处getMetaItems 把 overlay 行按 request.type 写回
SchemaRegistry。一次复数拼法的读取就铸出一个复数 registry 条目;从下一次读取起
listItems('actions') 不再为空,那条一直在提供 11 条代码编写 action 的单数回退再也不
执行 —— 一行 overlay 遮蔽整个代码编写清单,而且是在没有任何 DELETE 能寻址的拼法上,
所以它比那条本该抬走它的 DELETE 活得更久,最终 listing 与 dispatch 对一个已删除条目
各说各话。

改动

saveMetaItem / getMetaItem / getMetaItems / getMetaItemLayered /
getMetaItemCached / deleteMetaItem 一进门就把 type 折叠成规范单数(Prime Directive #3)。
静态数据的读取仍保留另一种拼法的回退:修复前以复数 type 写入的行是真实存在的,
升级时没有任何东西会重写它们。变的是:不再有任何地方写入注册非规范键。

验证证据

单测(packages/objectql/src/protocol-meta-type-canonicalization.test.ts,5 条)。撤掉折叠
后确实会红,实测失败两条:

× reading the plural spelling does not mint a phantom namespace
× both spellings resolve the same single item
  AssertionError: expected 'actions' to be 'action'

第一条正是复现报告里的步骤 3/5:第一次复数读取铸出 phantom,第二次读取就丢掉全部代码
编写的 action。

关于 save/delete 两条断言我在测试注释里如实写了它们在修复前就已成立
SysMetadataRepository 本来就折叠单数,落盘的行一直是规范的),保留它们是为了防止
将来从写入侧重新引入分裂 —— 没有把它们包装成"复现"。

复现报告第 6 点提到的另一个现象(saveMetaresolveRouteActionDeclaration
之间有缓存滞后,overlay 短时间内"列得出但派发不了")不在本 PR 范围,按 Prime
Directive #10 应另开 issue,我没有顺手扩大范围。


3. #4455os migrate value-shapes 漏掉 lookup 里的内嵌记录对象

根因

该扫描是 ADR-0104 D1 每部署闸门的证据侧,它自己的文件头点名了存在的理由:"location
存成 {latitude, longitude} lookup 里存了展开的记录对象"。第二种情形从未被检出:
ReferenceIdValueSchemaz.string().min(1),而在 SQL 部署里一个遗留的内嵌引用正是以
JSON 文本形式落在 TEXT 列 —— 一个非空字符串。于是恰好带着这个闸门存在意义的那类值的
部署,跑完扫描被告知"干净",然后用 --apply 关闭了闸门。又因为扫描刻意复用写路径谓词,
写路径同样是瞎的,这个值还能在后续写入中继续存活。

改动

ReferenceIdValueSchema 现在拒绝首个非空白字符是 {[ 的值,expanded 形式也一样
$expand 产出的是对象,永远不是它的序列化文本)。

刻意比 issue 的首个建议更窄。 它的 file 姐妹 FileReferenceIdValueSchema 能限定字符集,
是因为 sys_file id 由平台铸造、且只由平台铸造;而引用 id 是目标对象主键里的任何东西,
包括 ADR-0015 联邦 datasource 提供的外部键。所以这里只拒绝可证明不是 id 的形状,把
id 字母表留给拥有它的对象 —— CB0-2026-0001SFDC:001xx…ops/eu-west/tenant-7
全部保持有效,测试把这点钉死了。再放宽需要关于真实外部键的证据,而不是猜测。

验证证据

回归测试压在闸门上,不只压在 schema 上
packages/objectql/src/validation/scan-value-shapes.test.ts):种入序列化的内嵌记录 →
断言被计入 → 断言 valueShapeScanPassed() 为 false(该部署不得记录 flag)→ 再断言
同一个值在 strict 下是写入拒绝,也就是扫描与校验器仍然用同一个谓词回答问题。这一条
是针对本批次"测试替身比真实实现宽松"教训的直接防线。

通过 ADR-0104 的 warn-first 通道触达作者(一条 [value-shape] 日志),部署自己 opt-in
strict 之前不会有任何写入开始被拒 —— 升级不破坏运行中的应用;但扫描现在会计数,持有这类
值的部署再也关不上闸门。


4. #4449validateFormLayout 写了、测了、导出了,但没有任何命令调用它

根因

它有完整实现、完整单测、公开导出和已发布的规则 id,而全仓搜索除了实现、导出行和自己的
单测之外零个调用点
。它存在多久,就在零个 stack 上跑了多久。

改动

  1. 作为 advisory 注册进 AUTHORING_RULES,三条命令(os validate / os build /
    os lint)一起跑。它只走结构化 metadata、不加载任何重依赖,三条命令的成本可忽略。
  2. 给守卫补上反方向的闭合。 [cli/lint] 作者时规则的命令覆盖漂移是系统性的:23/26 条手工接线规则只跑部分命令,9 条 error 级有门禁盲区(os build 会发布 os lint 拒绝的流) #4409 的每一条不变量都是从 registry 出发往命令看的,
    这个视角天生看不见一条从没进过 registry 的规则 —— 和 test(cli): 钉住三个命令都跑 reference-integrity suite(#4384) #4402 的名单只守得住名单上的名字
    是同一个形状,只是高了一层。守卫现在把两个 registry 从 @objectstack/lint 公开桶的
    validate* / lint* 导出里减掉,差集必须为空或在 UNWIRED_RULE_LEDGER 里带理由。
    账本以空的形态落地:今天的差集恰好只有这一条规则。

验证证据

新测试在没有 registry 条目时会红(闭合直接报出 validateFormLayout 未接线)。另外补了一条
活性测试:不只断言"在 registry 里",还调用该条目自己的 run 适配器,断言一个该被挑刺的
stack 确实拿回两条 finding —— 成员资格不等于会产出输出,而 #4449 恰恰是一条"存在、单测全绿、
在任何真实 stack 上零输出"的规则。同时给导出扫描加了非空断言,避免正则失配导致差集因为
错误的原因为空。


门禁与验证

全部本地实跑(分片/串行执行以控内存):

结果
@objectstack/spec test 280 files / 7079 tests passed
@objectstack/objectql test 94 files / 1545 tests passed
@objectstack/metadata-protocol test 22 files / 163 tests passed
@objectstack/runtime test 72 files / 1026 tests passed
@objectstack/service-datasource test 11 files / 197 tests passed
@objectstack/cli test 67 files / 697 tests passed
@objectstack/dogfood shard 1/2 38 files / 235 passed, 1 skipped
@objectstack/dogfood shard 2/2 37 passed + 1 skipped files / 197 passed, 2 skipped
turbo run typecheck(六个受影响包) 57 tasks successful
spec check:generated 8/8 artifacts up to date

Dogfood Regression Gate 两个分片都在本地跑过并通过 —— 这是本项最需要盯的门禁,因为 #4462
改的正是启动路径。

Changeset 四份(Check Changeset 需要相对 base 的新增文件):
datasource-mapping-is-routing.mdmeta-canonical-type-segment.md
reference-id-embedded-record.mdform-layout-lint-wired.md

边界

没有移除或重命名任何可作者化的 spec key,没有删除公开导出(只新增
ObjectQLEngine.resolveMappedDatasource),没有协议破坏性变更。#4462 是可观察行为变化,
取舍理由见上文,changeset 里也写了升级指引。


Generated by Claude Code

claude added 4 commits August 1, 2026 13:44
…om the other side (#4449)

`validateFormLayout` was implemented, unit-tested, exported and given published
rule ids — and no command ever called it. A whole-repo search found the
implementation, the barrel export line and its own unit test, and nothing else:
the rule ran on zero stacks for as long as it existed.

Two changes:

  * register it in `AUTHORING_RULES` as `advisory` on all three commands. It
    walks structured metadata only (no lazy dependency), so `os validate`,
    `os build` and `os lint` pay nothing measurable for it.

  * add the reverse closure to the wiring guard. Every invariant #4409 shipped
    starts FROM a registry and looks at the commands, which cannot see a rule
    that never entered a registry — the same blind spot as #4402's name list,
    one layer up. The guard now subtracts both registries from the `validate*`
    / `lint*` symbols on `@objectstack/lint`'s public barrel; the difference
    must be empty or ledgered with a reason in `UNWIRED_RULE_LEDGER`, which
    ships empty because today's difference was exactly this one rule.

The new tests fail without the registry entry: the closure reports
`validateFormLayout` as unwired, and the liveness test asserts the entry's own
`run` adapter returns both findings for a stack that earns them — membership
alone is not evidence a rule produces output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
…id id (#4455)

`os migrate value-shapes` is the evidence half of the ADR-0104 D1
per-deployment gate, and the scan's own header names the case it exists for: "a
`location` stored as `{latitude, longitude}` or a `lookup` holding an expanded
record object". The second case was never detected. `ReferenceIdValueSchema`
was `z.string().min(1)`, and in a SQL deployment a legacy embedded reference
reaches storage as JSON TEXT — a non-empty string. So a deployment carrying
exactly the values the gate exists to find ran the scan, was told it was clean,
and closed the gate with `--apply`; and because the scan deliberately imports
the write-path predicate, the write path was equally blind, so the value also
survived future writes.

`ReferenceIdValueSchema` now rejects a value whose first non-space character is
`{` or `[`, in the expanded form too — `$expand` produces an object, never its
serialization.

Deliberately narrower than an id charset. `FileReferenceIdValueSchema` can
bound its alphabet because a `sys_file` id is minted by the platform and
nothing else; a reference id is whatever the target object's key holds,
including an external key an ADR-0015 federated datasource supplies. So this
rejects the shape that is provably not an id and leaves the alphabet to the
object that owns it — `CB0-2026-0001`, `SFDC:001xx…` and `ops/eu-west/tenant-7`
stay valid, and the tests pin that.

Regression coverage is at the GATE, not just the schema: the scan test plants
the serialized embedded record, asserts it is counted, and asserts
`valueShapeScanPassed()` is false — the deployment may not record the flag —
then asserts the same value is a write rejection under strict, so the scan and
the validator still answer with one predicate.

Reaches authors through the ADR-0104 warn-first path until a deployment opts
into strict, so nothing starts rejecting writes on upgrade.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
…4432)

#3985 taught the per-type gates to accept both spellings of the `/meta` type
segment. It did not FOLD them, so `/meta/actions/x` and `/meta/action/x`
addressed two namespaces and the layers below disagreed about which one an item
lived in — `SysMetadataRepository` folded to singular on its own, while the
authorization tier above it (`isOverlayAllowed`, `isArtifactBacked`), the
registry heal below it (`restoreArtifactRegistryView`) and the list hydration
all read the caller's spelling.

The damaging half was the hydration. `getMetaItems` registered overlay rows back
into the SchemaRegistry under `request.type`, so one plural-spelled read minted a
PLURAL registry entry; from the next read on `listItems('actions')` was no longer
empty, the singular fallback that had been supplying every code-authored action
stopped running, and one overlay row hid the entire code-authored listing — on a
spelling no DELETE addresses, so it outlived the delete that was meant to lift
it and left listing and dispatch disagreeing about a removed item.

`saveMetaItem`, `getMetaItem`, `getMetaItems`, `getMetaItemLayered`,
`getMetaItemCached` and `deleteMetaItem` now fold the type to its canonical
singular as their first act. Reads of data AT REST keep the other-spelling
fallback: rows written under a plural `type` before this fix are real and
nothing rewrites them on upgrade. What changed is that nothing WRITES or
REGISTERS a non-canonical key any more.

Regression tests fail without the fold: a plural-spelled read mints a phantom
`actions` registry entry and the second read drops the code-authored actions,
and `getMetaItem` echoes back the caller's spelling so a client can round-trip
it into a second namespace.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
… routing, not a hint (#4462)

Measured on `main` during the v17 verification: map an object to a Postgres
datasource with a bad URL and the boot SUCCEEDS, `/ready` answers 200, the
datasource name appears in ZERO log lines, the write returns 201 — and the row
is physically in the DEFAULT store. The operator finds out by opening the
database they declared and finding it empty.

Two causes, one per layer, and each is what makes fixing the other correct:

  * `ObjectQLEngine.getDriver` step 2 read `mapped && drivers.has(mapped)`, so a
    MATCHED mapping rule naming a datasource with no live driver fell silently
    through to the default driver. It now throws — `DatasourceUnavailableError`
    when the connect layer recorded a verdict (#3828), otherwise an error naming
    the object, the datasource and the two remedies. `default` still resolves
    onward: the default driver keeps its natural name (#3826), so
    `drivers.has('default')` is false by construction and step 5 IS how routing
    to it works.

  * ADR-0062 D2's phase-1 note deliberately excluded "mapped" from the
    auto-connect gate, to keep `examples/app-crm` byte-for-byte unchanged. That
    note priced only one side. Gate (d) now fires when a mapping rule routes at
    least one object to a datasource, and a `declared-auto` failure is FATAL with
    an operator-readable reason — the same call gate (b) already makes for an
    explicit `object.datasource` binding, correct for (d) now that routing no
    longer supplies a fallback. `OS_ALLOW_DRIVER_CONNECT_FAILURE` still degrades.

The mapped-object list comes from the engine's own matcher
(`ObjectQLEngine.resolveMappedDatasource`, newly public) via
`connectDeclared({ mappedObjects })`. The connection service never re-derives
rule matching: two matchers drifting by one clause would connect a datasource
routing never uses, or route to one nothing connects — the defect again.

`examples/app-crm`'s mapping is DELETED, and that is what keeps the example
unchanged rather than what breaks it. Its `namespace: 'crm'` rule never matched
(`namespace` is deprecated; no object sets it) and its `default: true →
crm_primary` rule routed everything to an unconnected `:memory:` datasource,
i.e. to the default store by fall-through. Honouring it would move the whole
app — platform objects included — onto a database empty on every boot.

Verified against a real boot on a private port, not only in unit tests:
  * unchanged CRM example boots healthy; crm_primary/crm_analytics stay
    `unvalidated` (metadata-only) exactly as before;
  * with a mapping to `postgres://…@127.0.0.1:1/nonexistent_db`, boot exits 1
    with "1 object(s) are routed to it by a datasourceMapping rule (crm_account)
    and have no fallback datasource — their reads/writes would otherwise land in
    a DIFFERENT database than the one they declare ⇒ fail-fast per ADR-0062 D5";
  * under OS_ALLOW_DRIVER_CONNECT_FAILURE=1 the degraded-boot banner carries the
    same sentence and the mapped object's seeds fail instead of silently
    populating the default store.

ADR-0062 D2 carries the amendment; the docs page and the data skill now state
that a mapping rule is routing and fails the boot when it cannot be honoured.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
@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 3:47pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling size/xl labels Aug 1, 2026
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 6 package(s): @objectstack/cli, @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/runtime, @objectstack/service-datasource, @objectstack/spec.

120 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 packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli, packages/runtime, @objectstack/spec)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli, @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/runtime, @objectstack/spec)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • 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/cli, @objectstack/runtime, 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 @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/runtime, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/runtime, packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime, @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 packages/objectql, @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/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli, @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql, @objectstack/runtime)
  • 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/cli, @objectstack/runtime, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/runtime, @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/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli, 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/metadata-protocol, @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli, @objectstack/objectql, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime, @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/cli, @objectstack/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/objectql, @objectstack/runtime, @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/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • 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 packages/objectql, @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/objectql, @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/cli, @objectstack/objectql, @objectstack/runtime, @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/cli, @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol, @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 marked this pull request as ready for review August 1, 2026 15:57
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 1, 2026
@os-zhuang
os-zhuang marked this pull request as draft August 1, 2026 16:02
@os-zhuang
os-zhuang marked this pull request as ready for review August 1, 2026 16:06
Merged via the queue into main with commit 63b33e6 Aug 1, 2026
21 checks passed
@os-zhuang
os-zhuang deleted the claude/v17-verification-defects-gnf9e6-datasource branch August 1, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment