docs(skills): objectstack-query teaches the served route for a related record's column, not the refused nested form - #20811
objectstack-fleet[bot] wants to merge 2 commits into
Conversation
…d record's column, not the refused nested form
The published query skill taught `{ relation: { field: value } }` under a
lookup as a working filter. The engine refuses that form on every driver
(`INVALID_FILTER` / 400) and names the route that works: filter the related
object first, then `$in` its ids (`$contains` per id on a multi-valued
lookup). The six sites that taught or pointed at the form now state the
refusal and the route; the rule section's rewrite is paid for inside
`rules/filters.md` by deleting three examples whose rule already lives in
`SKILL.md`.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg
…d (8 → 7) The deleted "Combining Logical Operators" example carried one `role: 'admin'` literal; `check:role-word` prescribes the ratchet-down and this is its `--update` output, one row. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg
Contract reviewServed-tier: Inputs. Card #20782: body and all three comments (triage 5905048525, claim 5906472951, Check-runs on ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
维护者速读(终稿)— PR #20811 · 查询技能:关联字段筛选改教可用的两步路线(#20782)skills 席 1 · 改了什么:改的是
为什么改:PR #20781 之后,所有驱动都拒绝这种写法。AI 照旧技能写,要么拿到 400,要么在内存驱动上静默返回 0 行。 风险与代价(含回滚):只改文档,不发布任何包。回滚方式是 revert。 席位意见:ACCEPT,建议批准。
你要做的:审阅后给 APPROVED,之后由本席位落地。 Generated by Claude Code |
Fixes #20782
Clause-②: no
What this changes
The published skill
skills/objectstack-querytaught{ relation: { field: value } }beneath a lookup as a workingwhereform. Onmainthe engine refuses that form on every driver —INVALID_FILTER/ 400, in the words ofrelationWords()inpackages/objectql/src/no-operator-object-door.ts— and names the route it serves: filter the related object first, then match the relation column against the ids it returns ($inon a single-valued column;$containsper id, an$orof those for several, on amultiple: trueone, whose JSON column the SQL driver refuses$inon). The skill now states that refusal and that route at every site that taught or pointed at the form. It anticipates neither letter of the open v18 decision (#20802 is not addressed here): it states today's behaviour.Six landing sites, all in
skills/objectstack-query, measured onorigin/main96e72447:96e72447)SKILL.md:76, Removed-key rowquery.joinsexpand, or a nested relation filter"expand(display), or filter the related object and$inits ids"SKILL.md:88, rules indexSKILL.md:187-:194, subsection{ contact: { profile: { verified: true } } }exampleSKILL.md:334$inids from its own query"SKILL.md:343, Cross-Object rowrules/filters.md:132-:152, sectionThe
:343row was mislabelled. "Filter parent by child conditions" names the reverse direction (a parent by its children's fields), while the form it pointed at, even had it been served, expresses only the forward one (rows by their lookup target's column). The row is now two: "Filter rows by their lookup target's column" → query the target object, then{ lookup: { $in: ids } }($containsper id whenmultiple); "Filter parent by child conditions" → query the child withfields: [lookup], then{ id: { $in: those ids } }on the parent. Both routes are served: the engine's ownexpandbatch-loads with{ id: { $in } }(engine.ts, the "Batch-load related records using $in query" block), and the REST ingress admitsidas a filter key (protocol.tsresolveQueryFields,known.add('id')).No dotted alternative is taught.
'owner.region'is refused one door earlier by the #8371 dotted verdict (filter-comparand-shape.ts,INVALID_FIELD/ 400); a grep of the skill for a dottedwherepath finds none.Census.
grep -rn -i -E 'nested relation|relation filter|nested relations' skills/on96e72447finds exactly the six sites; a multi-line shape grep for awhereexample nesting a no-operator object under a relation key finds the same three (SKILL.md:193,rules/filters.md:138,:145) and nothing else inskills/**.Paying for it inside
rules/filters.mdThe file sat at 2148 / 2149 tokens. The section rewrite (881 bytes) is paid for by deleting three examples whose rule already has a home in the same package, so the file lands at 2149 / 2149 (headroom 0, ceiling untouched):
rules/filters.md## Implicit Equality (Shorthand)(:40-:51)SKILL.md"Implicit Equality (Shorthand)" (:94-:101), and the$eqrow of this file's Operator Reference### NOT(:93-:100)SKILL.mdLogical Operators,{ where: { $not: { status: 'closed' } } }(:183-:184)### Combining Logical Operators(:102-:113)SKILL.md"AND + OR combined" (:173-:181); sibling-keys-are-AND is this file's first Common MistakeThe
role: 'admin'literal in the deleted third example movedcheck:role-word's count for the file 8 → 7; the gate prescribes the ratchet-down ("run--updateand commit the baseline"), and commitb893787bis that--updateoutput: one row ofscripts/role-word-baseline.json. That file is outside the claim's declared surface; declared here and in the report.skills/**readings (lines and tokens; tokens are the ratchet'sceil(utf8 bytes / 4))96e72447)b893787b)SKILL.mdrules/filters.mdskills/objectstack-query(6 files)Line budget (PM-set, net 0 at most): −39. No untouched line was re-wrapped; no ceiling row moved.
Verification
Gates, all at head
b893787b, exit codes captured by redirect and verdict lines quoted from each log: the 31 familiesdispatch-gates --commands --repo objectstack-ai/objectstackderives from the change set, reconciled with--ran("31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN") — every one exit 0, includingcheck-skills-token-ratchet("54 authored bundle file(s) within their ceilings") and its--self-test(65 cases),check:role-wordafter the ratchet-down ("OK, no new occurrences of the reserved word"),check:corpus-claim-drift,check:skill-identifier-liveness,check:doc-authoring,check:nul-bytes,check:doc-formula-expressions(@objectstack/formulaand@objectstack/lintbuilt first), speccheck:skill-docs("Skill docs in sync") and speccheck:skill-refs("9 generated files in sync").check:skill-examplesdoes not apply: no block in this skill carries anos:checkmarker.Pin: the skill's example validation does not cover these snippets (no
os:checkmarker), so the pin is PR #20781's, onmain:packages/objectql/src/engine-nested-object-door.test.ts(the refusal envelope{ code: 'INVALID_FILTER', status: 400 }beneathlookup,master_detail,multiple: truelookup,userandtree, and the CONTROL that{ owner: { $in: [...] } },{ owners: { $contains: ... } }, its$or, and{ id: { $in: [...] } }reach the driver as written) andpackages/rest/src/data-nested-object-door.test.ts(400 overPOST /api/v1/data/:object/query; the routes answerd1,d3). Run first-hand here:pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/engine-nested-object-door.test.ts→ "Test Files 1 passed (1), Tests 15 passed (15)". No package is touched, so no package build or test suite is owed beyond that.Acceptance notes
content/docs/protocol/objectql/query-syntax.mdx(:602-:610, read-only here): the "Filtering Across Relationships" callout's headline is true (neither the nested form nor a dotted path is served), but its mechanism paragraph is stale — it saysSqlDriver.applyFilters()compiles the nested object as a single-column comparison and emits the dotted key verbatim to Knex, while onmainboth are refused at the engine before any driver (INVALID_FILTER/ 400 for the nested form,INVALID_FIELD/ 400 for the dotted path), and it names no served route. Noted, not filed: documentation drift under a true headline; no carrier known.check:pm-governed-mergesas the package script spells it is the--self-testalone (exit 0 here); the live sweep is CI's.api_writeslists every relay write of this run.维护者速读(草稿)
改了什么。 发布的查询技能包
skills/objectstack-query原先把「在关联字段下直接写条件」({ customer: { country: 'US' } })当作能用的过滤写法来教。现在六处教它或指向它的句子都改成平台今天的真实行为:这种写法被引擎拒绝(INVALID_FILTER/ 400),可行的路是先查关联对象拿到 id,再对关联字段用$in(多值关联用$contains逐个 id)。规则文件里删掉了三段在 SKILL.md 已有同样规则的重复示例,用来支付这段改写;整个包净减 39 行,每个文件的 token 上限都没动。为什么改。 AI 作者照着技能包写,写出来的查询在内存驱动上静默返回 0 行、在 SQL 驱动上报 400;PR #20781 合并后所有驱动都统一拒绝。技能包是客户项目里 AI 的教材,教错一句就是每个客户项目里的错误查询。本 PR 不预判 v18 决策卡(#20802)的任一方向,只陈述今天的行为。
风险与代价(含回滚)。 只改文档文字与一行门禁基线(
role-word计数 8 → 7,门禁自己要求的下调),不改任何代码或发布包。风险在措辞:若维护者裁定 v18 支持关联过滤,这几句还要再改一次(决策卡已注明)。回滚即 revert 本 PR 的两个提交。席位意见。
你要做的。 复核六处措辞与三处删除各有归宿;批准后由席位落地(Tier H)。
Generated by Claude Code