Skip to content

feat(lint): 禁止把引擎查询选项擦成 any,存量入计数 baseline 只减不增 (#4918) - #5600

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-4918-query-options-erasure
Aug 5, 2026
Merged

feat(lint): 禁止把引擎查询选项擦成 any,存量入计数 baseline 只减不增 (#4918)#5600
os-zhuang merged 2 commits into
mainfrom
claude/issue-4918-query-options-erasure

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #4918

一、前提重测(派发口径的硬要求)

正文的测量基于 89d2a4e(08-03)。本分支基于 e900015cd(08-05,后并入 e0b2ea72d)重测,逐形状结果:

形状 非测试 测试
A. find / findOne / count / aggregate 的选项参数(第 1、2 位)被 any 断言擦掉 80 266
B. orderBy: … as any(比参数低一层,A 的选择器看不到) 1 0
C. 拆开写的 const opts: any = { … }find(o, opts)(作用域解析,非命名启发) 3 1
合计 84 处 / 19 文件 267 处 / 51 文件

比正文的 25+11 多,不是少 —— 前提成立,而且更强。这不是两天里长出来的:正文的 A 是按 } as any) 这类文本形状数的,AST 口径把同一个类的其它拼法(标识符参数、第 2 位的 hookContext.input.options as anyaggregate / count)一起算进来了。84 处的源行逐条人工核对过,零误报 —— 全部是真的 engine / driver 查询调用。存量清单进 scripts/query-options-erasure-baseline.json

二、形态选择:eslint 规则 + 计数 baseline(不是 scripts/ 独立走查)

判据是「哪种已有同形先例且能双向证红」。同形先例就在隔壁:#4251 的 slot-lookup 是同一个类(把已声明的契约擦成 any),它的形态是 eslint 规则 + ignores 按文件豁免 + 计数 shrink-only baseline + check-slot-lookup-ratchet.mjs 复测。本单照抄这套机制,理由:

  1. 擦除是 packages/** TS 源码上的词法 / 作用域形状,ESLint 已经在 CI 解析这片源码(pnpm lint),独立走查要再解析一遍;
  2. 更重要的是:未被豁免的文件会直接让 pnpm lint 变红,这是本地最快的信号。独立走查给不了这个。

⛔ 没有碰 packages/lint(那是 metadata graph 的 linter,不是仓库自身 TS 源码的 linter),所以也没有碰互斥声明里的 packages/lint/src/validate-react-page-props.ts

三、规则

新规则 query-options/no-any-erasure(eslint.config.mjs),覆盖三个形状:

断言链是整条走的,{ … } as any as EngineQueryOptions 也算 —— 那个拼法先把字面量对着 any 检查(等于不检查),再贴回契约的名字,擦除程度和裸 as any 一样,读起来却像有类型。

as unknown as EngineQueryOptions 故意不算。它点名了自己在绕过哪个契约、保留了调用其余部分的检查、而且 grep 得出来是个有意行为 —— 裸 as any 三条都不满足。所以它是「这个值就是故意越契约的」(例如一条断言引擎会拒绝未知选项的测试)的规范拼法。

为什么是独立 plugin 规则而不是再加三条 no-restricted-syntax 选择器,两个理由都是硬的:

  1. flat config 不合并规则选项。第二个块在 packages/** 上再设一次 no-restricted-syntax,会把 slot-lookup 那块的选择器列表在两块都命中的文件上整体替换掉,等于静默删掉那条规则。两个门禁还需要各自独立的 ignores(未清扫集合是不同的文件),一个共享块给不了。
  2. 拆开写的形状需要作用域分析把标识符解析回它的声明,esquery 表达不了 —— 这也是 slot-lookup/no-any-assignment 存在的原因。

已知残余(写出来而不是暗示):经由有类型的间接层发生的擦除 —— 形如 (o, q?: any) => engine.find(o, q) 的转发 helper,或者自身返回类型被标成 any 的 wrapper —— 擦除效果一样,本规则看不到。: any 形参转发进查询是个真实形状(约 50 处,几乎全是测试 double),判它需要调用图而不是单文件作用域,和 slot-lookup 自己的 KNOWN RESIDUAL 是同一条边界,同一个答案:属于有类型的 lint pass。

四、存量:baseline + 只减不增,不清扫

scripts/check-query-options-erasure-ratchet.mjs(pnpm check:query-options-erasure,在 lint.yml 里紧挨 slot-lookup ratchet)是让 baseline 有意义的那一半。

  • 非测试:按文件豁免。ignores 一条会让整个文件闭嘴,所以 baseline 带逐文件计数,由脚本把豁免解除后复测 —— 列表里的文件新长一处擦除,骑不了旧条目。计数涨、跌、文件清干净没删条目、以及往 baseline 里加文件(对比与 main 的 merge base),四种都判红。
  • 测试:用一个聚合的只减不增数字,不做逐文件计数。08-03 分诊把首刀划在非测试代码是对的:测试里有相当一部分擦除是正当的 —— engine-unknown-option.test.tsengine-wire-alias-reject.test.tssqlite-wasm-out-of-contract-filter-input.test.ts 这类以「越契约输入」为被测对象的测试,必须擦掉类型才能构造出 tsc 本来会拒绝的输入。逐文件 ratchet 会在一条正当的新拒绝测试上判红,而且没有诚实的补救动作;聚合数字有一个:把故意的那处写成 as unknown as EngineQueryOptions,它点名契约、也不进这个计数。这是我在字面口径上唯一的取舍,明写在 baseline 的 $comment 和脚本注释里,请 PM 复核。

本 PR 不清扫那 84 处,按派发口径。其中一部分是真的跨类型边界(objectql 的 hookContext.input.options、metadata loader 的 query bag),需要的是把边界类型补上,不是删断言 —— 属后续批次。

五、双向证红

方向在跑之前就定了,四组:

注入位置 预测 实测
新的未豁免非测试文件 pnpm lint 红 + ratchet 红 两者都红:__injected_probe.ts: NEW engine query-options erasure (1 site(s))
已豁免文件(protocol.ts)追加一处 pnpm lint 绿(ignores 让整个文件闭嘴 —— 这正是「看不见的那一步」)+ ratchet 红 lint 绿;ratchet 红:erasure count grew 6 → 7
测试文件追加一处 pnpm lint 绿(首刀不覆盖)+ ratchet 红 lint 绿;ratchet 红:test surface grew 267 → 268 site(s)
当前树(未注入) 全绿 pnpm lint 绿;ratchet:84 unswept non-test site(s) in 19 file(s), none new

第二行是这套机制存在的全部理由:ignores 藏得住的那一步,只有计数抓得到。

--self-test 把这些做成了可重跑的形式:10 个应报形状、10 个应静默的对照(含 Array.prototype.find、结果位断言、第 0 位断言、被遮蔽的内层有类型绑定、as unknown as 逃逸口)、豁免通道与测试 glob 通道的双向证明、以及 8 个 ratchet 比较用例(相等 / 新文件 / 涨 / 跌 / 文件清空 / baseline 加键 / 测试面涨 / 测试面跌)。ratchet 比较逻辑抽成了纯函数 diffRatchet,因为那正是「在干净树上跑绿」永远碰不到的那一半。

另外把 #4674 的成因原地三态复现了一遍,证明这条规则守的是真东西,而不是一个自造的形状。取 protocol.ts 的 audit-history 查询(#4720 修的那处),pnpm exec tsc --noEmit -p packages/metadata-protocol/tsconfig.json:

状态 第 3520 行 tsc 本门禁
main 现状 order: 'desc' 64 个既有错误,3520 行 0 个 绿
A:改回错键,加擦除 direction: 'desc' error TS2353 … 'direction' does not exist in type … @ 3520,51 绿(没有擦除)
B:错键 + } as any) direction: 'desc' 63 个错误,3520 行 0 个 —— 擦除精确地拿掉了那一个 红:count grew 6 → 7

即:擦除就是把编译错误变成静默的那一步,而这条门禁现在抓的就是擦除。注:@objectstack/metadata-protocol 目前还在 check:type-check-coverage 的 DEBT 台账里、没有 typecheck 脚本,所以那条 tsc 通道今天是本地 / 编辑器级的,不是 CI 门禁 —— 这让本门禁更必要,不是更不必要。

baselineKeysAddedSinceMergeBase(merge-base 单调性)这一段依赖 git,本 PR 里 baseline 在 base 上还不存在,所以它按设计报告自己没能跑(NOT verified: …)而不是静默通过;它的判定逻辑由 diffRatchetaddedBaselineKeys 用例覆盖。下一个 PR 起这条会真跑。

六、其它


Generated by Claude Code

… shrink-only baseline (#4918)

`IDataEngine.find/findOne/count/aggregate` declare their options as
`EngineQueryOptions` / `EngineCountOptions` / `EngineAggregateOptions`, and
`IDataDriver` declares the same slots as `QueryAST` + `DriverOptions`. For an
INTERNAL caller `tsc` is the only channel enforcing those keys: the protocol's
ingress normalizer never runs on a direct engine call, and the options schemas
are not `.strict()`, so an unknown key is silently DROPPED rather than
rejected. One `as any` on the options argument switches that off for the call
site while looking identical to code that has it.

That is #4674: two internal queries spelled their sort
`{ field, direction: 'desc' }` where the QueryAST shape is `SortNodeSchema` =
`{ field, order }`. Both drivers normalize off `.order` with no fallback, so
both ran ASCENDING, and because both carried a `limit` the wrong direction
changed WHICH ROWS came back — audit history returned the oldest events and
global search the stalest matches. #4720 restored the two sites, #4721 closed
the external (REST/RPC) callers with a strict schema plus an ingress
normalizer; this is the third leg, and it stops the shape regrowing internally.

New rule `query-options/no-any-erasure` (eslint.config.mjs), three shapes:

- an `any` assertion at argument 1 or 2 of a query method — argument 0 is the
  object NAME on every one of these signatures, which is also what keeps
  `Array.prototype.find(cb)` out of the rule entirely;
- `orderBy: … as any`, which sits one level below the argument and so is
  invisible to the argument-position check;
- the split form (`const opts: any = { … }` … `find(o, opts)`) — the shape
  #4674's global-search site actually used. Scope analysis, not a name
  heuristic; needs no type information, so it stays in the untyped lint pass.

The assertion chain is walked, so `{ … } as any as EngineQueryOptions` — which
checks the literal against nothing and then re-labels it with the contract —
is caught too. `as unknown as EngineQueryOptions` is deliberately NOT matched:
it names the contract being bypassed, keeps the rest of the call checked, and
greps as an intentional act, so it is the sanctioned spelling for input that is
deliberately off-contract (a test asserting the engine REJECTS an option).

It is a dedicated plugin rule rather than three more `no-restricted-syntax`
selectors because flat config does not MERGE rule options: a second block
setting `no-restricted-syntax` over `packages/**` would REPLACE the
slot-lookup block's selector list for every file both match, silently deleting
that rule. The two guards also need independent `ignores`.

Residual re-measured on the branch point (the issue's numbers were taken at
89d2a4e, two days and ~40 merges earlier): 84 non-test sites in 19 files and
267 in test code, none of them a false positive. Both go into
`scripts/query-options-erasure-baseline.json` and neither is swept here — part
of the residual is a real type boundary (objectql's `hookContext.input.options`,
the metadata loader's query bag) that needs the boundary type written, not the
assertion deleted.

`scripts/check-query-options-erasure-ratchet.mjs` (`pnpm
check:query-options-erasure`, wired into lint.yml next to the slot-lookup
ratchet) is what makes the baseline mean something. Non-test files are
grandfathered by path, and an `ignores` entry silences the WHOLE file, so the
baseline carries per-file counts measured with the grandfathering lifted — a
new erasure in a listed file cannot ride the old entry. Test code is held by
one aggregate decrease-only number instead of per-file counts, because an
unknown share of those sites are legitimate and a per-file ratchet would go red
on a new rejection test with no honest remedy.

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

vercel Bot commented Aug 5, 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 5, 2026 8:23pm

Request Review

@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 5, 2026 — with Claude
@github-actions github-actions Bot added ci/cd dependencies Pull requests that update a dependency file size/l labels Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

CI 归因(devx 车道 PM,一次性说明,后续不逐事件复述):本 PR 当前两个红均非本 PR 所致——

  1. ESLint job 红 = main 自带:check:engine-double-contract 挂在 fix(runtime): callData 的 ObjectQL 兜底对「记录不存在」统一答 404 RECORD_NOT_FOUND (#5138) #5584(20:0xZ 合入)引入的 action-execution-calldata-not-found.test.ts(2 个 double 未接 assertEngineDeleteDispatch、基线无条目),对 origin/main 实证成立,已由分诊座位立案 main 全仓红:check:engine-double-contract 挂在 #5584 刚落地的 action-execution-calldata-not-found.test.ts(2 个 double 未接 assertEngineDeleteDispatch,基线无条目)——所有新 PR 的 ESLint job 都过不去 #5604(domain:cli)。该 job 不在队列必需四项内故 fix(runtime): callData 的 ObjectQL 兜底对「记录不存在」统一答 404 RECORD_NOT_FOUND (#5138) #5584 带病过队。处置:等 main 全仓红:check:engine-double-contract 挂在 #5584 刚落地的 action-execution-calldata-not-found.test.ts(2 个 double 未接 assertEngineDeleteDispatch,基线无条目)——所有新 PR 的 ESLint job 都过不去 #5604 修复落地后合 main 重跑;届时本 PR 若仍有 ESLint 红才归本 PR 诊断。
  2. Check Changeset 红 = 加标签前载荷竞态(Check Changeset 从事件载荷读 skip-changeset 标签:开 PR 后 5 秒内加标签,首个 run 永久红(重跑复用载荷)—— 一日三例 #5580 已立案的第 4 例,skip-changeset 标签在位,后续 run skipped)。

本 PR 自身的门禁(新规则 + ratchet)在其本地与 CI 输出中均绿。


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

CI 现状:ESLint job 红,但不是本 PR

本 PR 的 ESLint job 挂在 check:engine-double-contract,报的是 packages/runtime/src/action-execution-calldata-not-found.test.ts 的 2 个 engine double 没接 assertEngineDeleteDispatch那个文件是 #5584(43ca399)刚落地的,本 PR 一行未碰;已由另一个 agent 立为 #5604(main 全仓红,所有新 PR 的 ESLint job 都过不去),故不重复开单。

证据两条:

  1. lint.yml 的 ESLint job 步骤是顺序执行、失败即中止的,而日志显示它走过了本 PR 新增的 Engine query-options erasure ratchet(位置紧随 Slot-lookup ratchet),一路到 check:published-filescheck:engine-double-contract 才失败 —— 即本门禁在 CI 里是绿的。
  2. origin/main = e0b2ea72d(fix(runtime): callData 的 ObjectQL 兜底对「记录不存在」统一答 404 RECORD_NOT_FOUND (#5138) #5584 落地之前)+ 本 PR 的树上本地跑 pnpm check:engine-double-contract 是绿的(OK — 24 pinned, 31 in the DEBT ledger, 1 exempt);CI 用的 base 已经是 91ec1ead9(fix(runtime): callData 的 ObjectQL 兜底对「记录不存在」统一答 404 RECORD_NOT_FOUND (#5138) #5584 之后),差别就是那一个新文件。

已把当前 origin/main(a7b854f19)并入本分支并重测:84 / 267 两个数字未变(新落地的 4 个测试文件没有引入 query-options 擦除),pnpm lint 绿、pnpm check:query-options-erasure 绿、node scripts/check-nul-bytes.mjs 绿。本 PR 会在 #5604 修好后自然转绿,不需要为它改动任何东西。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 5, 2026 20:25
@os-zhuang
os-zhuang enabled auto-merge August 5, 2026 20:27
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 79b7fa1 Aug 5, 2026
23 of 24 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4918-query-options-erasure branch August 5, 2026 20:46
os-zhuang pushed a commit that referenced this pull request Aug 5, 2026
… the migrated tests (#4645)

The 15 turso test files arrived from cloud, which predates this repo's
`query-options/no-any-erasure` rule (#5600, landed on main after the first
merge), and added 52 sites — test surface 267 -> 319. Every one is inside
`packages/drivers/driver-turso/src/*.test.ts`; nothing outside turso moved.

Both dispositions the rule names, chosen per site, with no assertion touched:

- 14 sites TYPED. Ordinary on-contract reads whose only defect was the missing
  required `QueryAST.object` key (`driver.count('users', { where: { age: 17 } })`,
  the TEMPORAL_CASES/TEMPORAL_TIME_CASES conformance reads, and the `$not`/`$or`
  spellings the transport is asserted to ANSWER). Declaring `object` is what
  driver-memory's conformance suite already does, and it puts `where` back under
  tsc.
- 38 sites `as unknown as QueryAST`. Deliberately off-contract input: values
  flowing through the suites' `unknown`-typed helpers (`compile`, `refusalOf`,
  `ids`) and inline literals whose whole point is refusal — `where: 42`, `[]`,
  `''`, a class instance, bare AST arrays, `{ $or: [null] }`, `{ $not: null }`,
  non-boolean `$null` comparands. These assert the transport REJECTS them, so
  the type must be bypassed by name rather than erased.

Baseline untouched: 267 is met, not raised.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
os-zhuang pushed a commit that referenced this pull request Aug 6, 2026
`check:query-options-erasure`(PR #5600 / #4918,在本分支切出之后落地)
把测试面计入只增不减的棘轮。本分支新增的正控里
`engine.find('task', {} as any)` 是其中一处擦除,使测试面 267 → 268
而红。

该调用点是**合约内**形状——`find(object, query?: EngineQueryOptions)`
的空查询——不是「断言引擎拒绝未知选项」那类刻意越契约的输入,所以按
门禁处方的第 1 条直接给它正确类型(这里等于去掉断言,签名本就能推
断),而不是写 `as unknown as EngineQueryOptions`。

基线文件未动:抬高天花板是「reviewed edit, not a remedy」,这里修的是
站点本身。

验证(合并 origin/main 后):
- node scripts/check-query-options-erasure-ratchet.mjs → 267,at the
  ceiling,no files added
- @objectstack/objectql test → 1934 passed (120 files)
- @objectstack/spec + @objectstack/objectql typecheck → Done

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
…vers/ (objectstack-ai#4645 Phase A) (objectstack-ai#5618)

* feat(drivers): migrate driver-turso back in-repo and collect all IDataDriver packages under packages/drivers/ (objectstack-ai#4645)

Phase A of the maintainer-ruled two-repo migration (objectstack half).

- git mv packages/plugins/driver-{memory,mongodb,sql,sqlite-wasm} -> packages/drivers/*
  (content unchanged; only repository.directory follows the move)
- new packages/drivers/driver-turso, migrated from objectstack-ai/cloud @ bc372e3b,
  published Apache-2.0 / access: public. multi-tenant routing and vector-poc stay in cloud.
- pnpm-workspace glob, check-driver-conformance DRIVERS_DIR, both CI workflows,
  liveness evidence paths, adr-anchors, docs and source comments follow the move
- driver-turso enters the conformance matrix with 3 measured DEBT cells (objectstack-ai#5590)
- CLI turso refusal message no longer claims the package ships elsewhere (objectstack-ai#5602)

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

* fix(service-analytics): point the incoming applyLike cross-reference at packages/drivers/driver-sql (objectstack-ai#4645)

`like-pattern.ts` arrived with origin/main (objectstack-ai#5567) naming
`packages/plugins/driver-sql/src/sql-driver.ts` — the pre-objectstack-ai#4645 path. The merge
was textually clean because the file is new on main and the move is on this
branch, which is the jointly-wrong shape AGENTS.md §10 warns about: nothing
conflicts, and the cross-reference the header leans on lands nowhere.

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

* fix(driver-turso): satisfy the objectstack-ai#4918 query-options-erasure ratchet on the migrated tests (objectstack-ai#4645)

The 15 turso test files arrived from cloud, which predates this repo's
`query-options/no-any-erasure` rule (objectstack-ai#5600, landed on main after the first
merge), and added 52 sites — test surface 267 -> 319. Every one is inside
`packages/drivers/driver-turso/src/*.test.ts`; nothing outside turso moved.

Both dispositions the rule names, chosen per site, with no assertion touched:

- 14 sites TYPED. Ordinary on-contract reads whose only defect was the missing
  required `QueryAST.object` key (`driver.count('users', { where: { age: 17 } })`,
  the TEMPORAL_CASES/TEMPORAL_TIME_CASES conformance reads, and the `$not`/`$or`
  spellings the transport is asserted to ANSWER). Declaring `object` is what
  driver-memory's conformance suite already does, and it puts `where` back under
  tsc.
- 38 sites `as unknown as QueryAST`. Deliberately off-contract input: values
  flowing through the suites' `unknown`-typed helpers (`compile`, `refusalOf`,
  `ids`) and inline literals whose whole point is refusal — `where: 42`, `[]`,
  `''`, a class instance, bare AST arrays, `{ $or: [null] }`, `{ $not: null }`,
  non-boolean `$null` comparands. These assert the transport REJECTS them, so
  the type must be bypassed by name rather than erased.

Baseline untouched: 267 is met, not raised.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
…bjectstack-ai#5668)

* docs(spec): HookContext.input 契约表改成引擎真正构造的形状 (objectstack-ai#5273)

The `HookContext.input` table named three keys no producer sets:
`ast` on bulk update AND bulk delete, and `doc` on insert. `input` is
`z.record(z.string(), z.unknown())` — an open shape — so Zod validated
none of it and the prose was the only contract an author could read.

- Bulk writes carry no `ast`. The row-scoping predicate lives on the
  engine-internal `OperationContext.ast` (objectstack-ai#2982) so middleware-composed
  filters bind the driver call where no handler can widen them. Deleted
  the "the row-scoping predicate is carried in `input.ast`" sentence.
- `input.id` on a bulk before-event is present but `undefined` (the
  engine builds `{ id, … }` with shorthand), not absent — documented as
  such, since `'id' in input` answers true.
- Documented the post-objectstack-ai#5038 per-row after-event shape: `after*` on a
  bulk write dispatches once per matched row on a single-record-shaped
  context, so `input.id` IS bound there.
- insert builds `{ data }`, not `{ doc }`.

Kept: before-events still fire once per batch, and there is no `*Many`
event.

No engine change. The `ast` special-case in `hook-wrappers.ts`
deliberately stays — it is live on the READ path, where `input.ast` is
real and a handler may rewrite it.

Pinned in `packages/objectql/src/hook-input-shape-contract.test.ts`:
spec cannot execute a dispatch (objectql depends on spec, so a
spec-side test would invert the dependency), so the facts are asserted
next to the engine that produces them. `beforeFind` is the positive
control (objectstack-ai#4865) — it really does carry `ast`, so "no ast on writes" is
a measurement rather than a vacuous pass.

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

* test(objectql): 正控的 find 选项不再擦成 `any`,过 objectstack-ai#4918 query-options 棘轮

`check:query-options-erasure`(PR objectstack-ai#5600 / objectstack-ai#4918,在本分支切出之后落地)
把测试面计入只增不减的棘轮。本分支新增的正控里
`engine.find('task', {} as any)` 是其中一处擦除,使测试面 267 → 268
而红。

该调用点是**合约内**形状——`find(object, query?: EngineQueryOptions)`
的空查询——不是「断言引擎拒绝未知选项」那类刻意越契约的输入,所以按
门禁处方的第 1 条直接给它正确类型(这里等于去掉断言,签名本就能推
断),而不是写 `as unknown as EngineQueryOptions`。

基线文件未动:抬高天花板是「reviewed edit, not a remedy」,这里修的是
站点本身。

验证(合并 origin/main 后):
- node scripts/check-query-options-erasure-ratchet.mjs → 267,at the
  ceiling,no files added
- @objectstack/objectql test → 1934 passed (120 files)
- @objectstack/spec + @objectstack/objectql typecheck → Done

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

---------

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

ci/cd dependencies Pull requests that update a dependency file size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lint 规则:禁止对引擎/驱动查询选项做 as any / : any 擦除(#4721 的顺带项,已实测残余量)

2 participants