Repository navigation
[finding] skills/objectstack-query teaches the nested relation filter { relation: { field: value } } as a working form; no data-path driver serves it, and PR #20781 makes the engine refuse it #20782
Description
Activity
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsTriage: first grade —
documentation·priority:p2·domain:skills·area:api·pm:queue. Direction: the skill teaches what works today and says the engine refuses the nested form. It is dispatched after PR #20781 landsTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T05:54Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
skills/objectstack-query(SKILL.md,rules/filters.md), a governed surface (Tier H: the maintainer reviews by hand) ⇒domain:skills. It is the skills half of #20745.Why p2. It takes #20745's grade (p2). The published, AI-facing skill teaches a filter that:
- answers no rows on memory today;
- is refused on SQL;
- will be refused everywhere once PR fix(objectql)!: a no-operator object beneath a relation, structured-JSON or undeclared id column is refused INVALID_FILTER / 400 on every driver (#20745) #20781 lands.
AI authors copy it.
Direction.
- What works today. The four sites (
SKILL.md:74,:185,:332,:341andrules/filters.md:132) teach it: filter the related object, then$inon its ids;$containsper id on a multi-valued relation. They state that the engine refuses a no-operator object under a relation field, in PR fix(objectql)!: a no-operator object beneath a relation, structured-JSON or undeclared id column is refused INVALID_FILTER / 400 on every driver (#20745) #20781's own refusal words. - ⛔ No promise of serving. Whether the platform serves relation filters is an open maintainer question (raised on [finding] a no-operator object under a lookup, master_detail or json field answers per driver: the declared nested-relation filter returns no rows on memory and a 400 on SQL, and a json object comparand deep-equals on memory and is refused on SQL #20745; its natural home is the v18 convergence, [finding] 仓内存在 5 个独立的过滤器→谓词编译器,每次语义裁决成本 ×5 —— 值得立「谓词编译收敛」调查程序(#5298 成本清单副产品) #5930). If it is ruled yes, the skill changes again then.
- Serial. After PR fix(objectql)!: a no-operator object beneath a relation, structured-JSON or undeclared id column is refused INVALID_FILTER / 400 on every driver (#20745) #20781 merges, so the text states the refusal as shipped.
- The pin is the skill's existing example validation, if it covers these snippets. Otherwise the example is shown refused by the engine and the
$inroute answering the intended rows ([finding] a no-operator object under a lookup, master_detail or json field answers per driver: the declared nested-relation filter returns no rows on memory and a 400 on SQL, and a json object comparand deep-equals on memory and is refused on SQL #20745's table).
- addedarea:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsThe API a customer can call, and integrations — REST, connectors, webhooks, jobsdocumentationImprovements or additions to documentationImprovements or additions to documentationpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 30, 2026 objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsClaim: PM loop round 3
Session:session_01KTZmMfzVzjNvyaLyQ8mHvg
Account:os-warren(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-20782-query-skill-relation-filter
Worktree:objectstack-issue-20782
Domain:domain:skills
Seat:domain:skills#1
File surface:skills/objectstack-query/SKILL.md, the five sites that teach or point at the nested relation form: the Removed-key table rowquery.joins(:76), the rules index line (:88, "nested relations"), the "Nested Relation Filters" subsection (:187-:194), thesearch never traversessentence (:334), and the Cross-Object table row (:343).skills/objectstack-query/rules/filters.md, the "Nested Relation Filters" section (:132to the section's end, before "Common Mistakes").scripts/check-skills-token-ratchet.mjsonly if a ceiling row is lowered. The rest ofskills/**is read for the census (no other hit onorigin/main96e72447).content/docs/**is read only, ⛔ not edited: PR #20781 already correcteddata-engine.mdx, and any other hit is reported for the seat to card. Stop on breach; explain in the report.
Container & model:S, not mechanical ⇒ M treatment,mode:subagent,model: claude-fable-5-1(dispatch-gates --tier: Model tier — MANDATORY: claude-fable-5-1 (derived from the file surface, not recalled);skills/**clause ①)
Clause-②: no
Thread-read: 5905048525
Serial constraints cleared:- The triage's serial condition holds: PR fix(objectql)!: a no-operator object beneath a relation, structured-JSON or undeclared id column is refused INVALID_FILTER / 400 on every driver (#20745) #20781 ([finding] a no-operator object under a lookup, master_detail or json field answers per driver: the declared nested-relation filter returns no rows on memory and a 400 on SQL, and a json object comparand deep-equals on memory and is refused on SQL #20745) MERGED as
4b4ee88f(2026-09-30T06:01Z), so the refusal is shipped and its words are onmain(packages/objectql/src/no-operator-object-door.ts,relationWords()). - The file's previous claim ([finding]
skills/objectstack-query/SKILL.mdtells an agent that engineaggregaterefusessearch/searchFields— false since PR #20487 #20500) landed as PR docs(skills): objectstack-query — engine aggregate row admits search / searchFields #20776, and its:26/:38-:44edits are onmain. - No open PR touches
skills/objectstack-query/**(the eleven open PRs' file lists, read at this claim). - This lane's other in-flight card, finding(fleet-write): an
issue_patchbody of about 41 KB comes back with two multi-byte characters replaced by U+FFFD, about 16 KB apart, identically on a re-send — anddispatch.mjsreports success without comparing the read-back #20571, touches no path here. - The v18 decision card [Decision] v18:查询能否直接按关联记录的字段筛选(例:「客户行业 = 科技」的商机) #20802 (
needs-user-decision,domain:engine) does not hold this card: the triage direction (5905048525) says the skill teaches what works today, and it changes again only if serving is ruled in.
Readings at 2026-09-30T07:36Z on
origin/main96e72447:rules/filters.md: 2148 / 2149 tokens (headroom 1), so the section's rewrite must pay for itself inside the file.SKILL.md: 401 lines, 3990 / 5552 tokens.evals/filters-pagination-search.json: no nested-relation case.
Generated by Claude Code
- The triage's serial condition holds: PR fix(objectql)!: a no-operator object beneath a relation, structured-JSON or undeclared id column is refused INVALID_FILTER / 400 on every driver (#20745) #20781 ([finding] a no-operator object under a lookup, master_detail or json field answers per driver: the declared nested-relation filter returns no rows on memory and a 400 on SQL, and a json object comparand deep-equals on memory and is refused on SQL #20745) MERGED as
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 20782, "status": "done", "branch": "claude/issue-20782-query-skill-relation-filter", "pr": "https://github.com/objectstack-ai/objectstack/pull/20811", "head_sha": "b893787bfd1323878680c47487e47c8ef6b47130", "base_sha": "96e724475c476d018c2b6d13dcd117ade14d0aa0", "session": "session_01KTZmMfzVzjNvyaLyQ8mHvg", "premise_still_valid": true, "summary": "The published skill `skills/objectstack-query` taught `{ relation: { field: value } }` beneath a lookup as a working `where` form; on `main` the engine refuses it on every driver (`INVALID_FILTER` / 400, `relationWords()` in `packages/objectql/src/no-operator-object-door.ts`) and names the served route. All six landing sites (`SKILL.md:76`, `:88`, `:187-:194`, `:334`, `:343`; `rules/filters.md:132-:152`) now state the refusal and the route: filter the related object first, then `$in` its ids; `$contains` per id (an `$or` for several) on a `multiple: true` lookup, whose JSON column the SQL driver refuses `$in` on. The `:343` row was mislabelled (it named the reverse direction, parent by child, while the form it pointed at expresses only rows by their lookup target); it is now two rows, one per direction, each a served route (`{ lookup: { $in: ids } }`; child query with `fields: [lookup]` then `{ id: { $in } }` on the parent, the shape the engine's own `expand` batch-load uses and the REST ingress admits via `known.add('id')`). No dotted alternative is taught (the #8371 door refuses it `INVALID_FIELD` / 400 one door earlier). Nothing anticipates either letter of decision card #20802. The rewrite is paid for inside `rules/filters.md` by deleting three examples whose rule has a home in `SKILL.md` (shorthand equality `:94-:101`; `$not` `:183-:184`; mixed AND/OR `:173-:181`); the file lands at 2149 / 2149 tokens. The deleted `role: 'admin'` literal moved `check:role-word` to 8 → 7 and the gate prescribed the ratchet-down, committed as `b893787b` (one row of `scripts/role-word-baseline.json`).", "mechanism_assumptions": { "1_refusal": "holds — `noOperatorObjectColumnKind()` gives `relation` for `REFERENCE_VALUE_TYPES` (`lookup`, `master_detail`, `user`, `tree`), single or multiple; `relationWords()` names `$in` (single) or `$contains` per id with an `$or` for several (multiple); envelope `{ code: 'INVALID_FILTER', status: 400 }` pinned in `packages/objectql/src/engine-nested-object-door.test.ts` and `packages/rest/src/data-nested-object-door.test.ts` (400 over `POST /api/v1/data/:object/query`); the REST suite's header table also records `{ owners: { $in } }` on a multiple lookup answering 400 on both SQL cells, which is why the multi-valued spelling is `$contains`", "2_dotted": "holds — `filter-comparand-shape.ts` judges a dotted head through `classifyDottedFilterHead` and throws `INVALID_FIELD` / 400; the skill suggests no dotted `where` path (grep: none)", "3_sites": "holds — six sites, exactly as listed; census `grep -rn -i -E 'nested relation|relation filter|nested relations' skills/` and a multi-line shape grep for a no-operator object under a relation key in a `where` example find nothing else in `skills/**`", "4_row_343": "mislabelled — 'Filter parent by child conditions' is the reverse direction; the refused form covers only the forward one; both directions written as served two-step routes (see summary)", "5_pins": "no `os:check` marker anywhere in `skills/objectstack-query`, so `check:skill-examples` does not apply; evals carry no relation case (none added: the package budget is net-negative by design and the pin lives in PR #20781's suites on `main`)", "6_budget": "held — `rules/filters.md` 2148 → 2149 / 2149; `SKILL.md` 3990 → 4055 / 5552; no untouched line re-wrapped, no ceiling row moved" }, "files_changed": [ "skills/objectstack-query/SKILL.md (401 → 399 lines; 3990 → 4055 tokens, ceiling 5552)", "skills/objectstack-query/rules/filters.md (251 → 214 lines; 2148 → 2149 tokens, ceiling 2149)", "scripts/role-word-baseline.json (one row: `skills/objectstack-query/rules/filters.md` 8 → 7 — gate-prescribed `--update` output; outside the declared surface, see deviations)" ], "line_budget": { "rule": "PM-set: net 0 lines at most across the published package; every per-file token ceiling held", "touched_files_before_after": "SKILL.md 401 → 399 lines / 3990 → 4055 tokens; rules/filters.md 251 → 214 lines / 2148 → 2149 tokens", "package_before_after": "skills/objectstack-query (6 files): 1184 → 1145 lines (−39); 10461 → 10527 tokens (+66; the ratchet is per file and every ceiling holds)", "net_lines": -39, "ceilings": "unchanged; headroom SKILL.md 1497, rules/filters.md 0" }, "tests": "No package touched ⇒ no ① build closure and no ② package suite owed. Gates at head b893787b (exit codes captured by redirect-then-$?, verdict lines from each log; 31 families derived by `dispatch-gates --commands --repo objectstack-ai/objectstack` from the merge-base change set, reconciled `--ran`: '31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN'): check-ci-filter-parity 0 · check-closing-keyword-parity 0 (+ --self-test 0, 40 assertions) · check-comment-mask-corpus 0 (7609 files, 0 disagree) · check-doc-route-spelling --advisory 0 (+ --self-test 0) · check-skills-token-ratchet 0 ('54 authored bundle file(s) within their ceilings'; SKILL.md 4055/5552, rules/filters.md 2149/2149) (+ --self-test 0, 65 cases) · lint check:doc-formula-expressions 0 (formula + lint built under os-verify-lock first, VERDICT command-exit 0) · spec check:skill-docs 0 ('Skill docs in sync') · spec check:skill-refs 0 ('9 generated files in sync') · check:agent-test-spelling 0 · check:corpus-claim-drift 0 ('OK, no new claim sites beside a pinned spelling') · check:cross-package-test-inputs 0 · check:doc-authoring 0 · check:driver-memory-census 0 · check:gitlink-declared 0 · check:nul-bytes 0 ('9417 text file(s) … no raw ASCII control bytes') · check:pm-governed-merges 0 (the package script is its --self-test) · check:refd-timer-probe 0 · check:role-word 1 on the first run at 1afde54f ('count improved 8 → 7 — ratchet DOWN: run --update'), then 0 after the prescribed --update ('OK, no new occurrences of the reserved word'; re-run at b893787b: 0) · check:skill-compatibility 0 · check:skill-frame-sync 0 · check:skill-identifier-liveness 0 (Leg 1 457 citations, Leg 2 8 sections) · check:watch-hint-literal 0 · added by the baseline row's scripts/** hit: check-scripts-symbol-anchors 0 (+ --self-test 0) · check:bash32-floor 0 · check:cli-command-ids 0 · check:entry-guard 0 · check:parse-guard 0 · check:pnpm-filter-targets 0. Ratchet families re-run at the final head b893787b: token ratchet 0, role-word 0, corpus-claim-drift 0. check:skill-examples: not applicable (no os:check marker). Pin, run first-hand under os-verify-lock (VERDICT command-exit 0, held 12s): `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)' (objectql's dependency closure built first, VERDICT command-exit 0, held 120s). NOT MEASURED locally: `packages/rest/src/data-nested-object-door.test.ts`, reason: no package touched; it is PR #20781's pin on main and CI runs it. Ablation: none (no gate or code changed).", "ci": "in_progress at report time (PR #20811 opened at b893787b; not waited for, per contract)", "labels": "PR #20811 via label-write (relay run 36687532982): +skip-changeset (`skills/**` ships in no package's files[]), assignee os-warren; read-back MATCHES the target: labels documentation, size/s, skip-changeset (documentation and size/s were set by other actors; untouched) · assignees os-warren", "mcp_calls": "0 — no MCP GitHub tool called", "api_writes": "3 relay writes (each ONE repository_dispatch executed by fleet-write as objectstack-fleet[bot]) + 3 git pushes: git push (empty-branch probe, 96e72447) · git push (1afde54f) · git push (b893787b) · pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft forced; relay run 36687433868, request issue-20782-pr-create) · label-write → POST /repos/objectstack-ai/objectstack/issues/20811/labels + POST /repos/objectstack-ai/objectstack/issues/20811/assignees (one dispatch, relay run 36687532982) · os-dev-report comment → POST /repos/objectstack-ai/objectstack/issues/20782/comments (this comment, via post-stamped). Reads, header-less GETs through the container proxy: issues/20782, issues/comments/5905048525, issues/comments/5906472951, issues/20802, pulls?head=… (PR read-back).", "deviations": [ "scripts/role-word-baseline.json edited (one row, 8 → 7) — outside the claim's declared file surface. Cause: the deleted `Combining Logical Operators` example carried a `role: 'admin'` literal; `check:role-word` reds on the shrink and prescribes `--update` + commit as the author's own step. Declared in the PR body and here; the alternative (keep the example) does not fit the 2149-token ceiling.", "Commit trailers carry the model-free pair per AGENTS.md (`Co-authored-by: Claude` + `Claude-Session:`); the harness attribution reminder asked for a model-named trailer, which the pre-push hook refuses — reporting, not followed." ], "open_questions": [], "out_of_scope_findings": [ "carrier: 承接者:无 · content/docs/protocol/objectql/query-syntax.mdx :602-:610 — the 'Filtering Across Relationships' callout's headline is still true (neither the nested form nor a dotted path is served), but its mechanism paragraph is stale: it says SqlDriver.applyFilters() compiles the nested object as a single-column comparison and emits the dotted key verbatim to Knex, while on main both 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. Documentation drift under a true headline (not (a)/(b)/(c)); noted in the PR's Acceptance notes, not filed. Dedupe words: query-syntax.mdx relation traversal callout · nested form dotted path refused engine · Filtering Across Relationships" ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsACCEPT — PR #20811 @
b893787b(skills seat 1,session_01KTZmMfzVzjNvyaLyQ8mHvg, 2026-09-30T14:07Z)Implemented-by:
claude/issue-20782-query-skill-relation-filter(dev report 5906976148). Reviewed-by:session_01KTZmMfzVzjNvyaLyQ8mHvg.- Shape.
- Draft, base
main. - First body line
Fixes #20782;Clause-②: noat a line start. - Files:
skills/objectstack-query/SKILL.md(+9 / −11),rules/filters.md(+14 / −51), and one row ofscripts/role-word-baseline.json. - Tier H (
skills/**).
- Draft, base
- Diff, read by the seat.
- All six sites now state the refusal (
INVALID_FILTER/ 400 on every driver) and the served route: query the related object, then$in, or$containsper id on amultiple: truelookup. - The parent-by-child row is split into its two directions.
- Nothing anticipates the v18 decision.
- All six sites now state the refusal (
- Contract review: PASS 5912934502 on this head. It was rendered at
CONTRACT_REVIEW_TIERby an isolated subagent (the transcript servedclaude-fable-5-1) and adopted by the seat.- No door applies a default row limit to
engine.find. The spec'slimithas no default, and the SQL and memory drivers applyLIMITonly when one is given. So the taught two-step route cannot truncate silently, and it rightly names no limit. - The three deleted examples each keep a home in
SKILL.md, and the role-word row is the gate's own prescription.
- No door applies a default row limit to
- Budgets.
- The package goes from 1184 to 1145 lines, net −39.
SKILL.mdis at 4055 / 5552 tokens andrules/filters.mdat 2149 / 2149. No ceiling moved.
- Changeset.
skip-changeseton the PR; assigneeos-warren. - CI on
b893787b. 24successand 11 path-filteredskipped. Every required context issuccessor path-skipped. - Out of scope.
- The reviewer corrected the dev's
carrier: noneoncontent/docs/protocol/objectql/query-syntax.mdx:605-:610. The callout's headline and its two-query example are true, but its mechanism sentences predate the engine refusal (4b4ee88f). The seat cards it for the docs lane. - Context only: decision [Decision] v18:查询能否直接按关联记录的字段筛选(例:「客户行业 = 科技」的商机) #20802 was ruled letter A after this PR opened. The skill changes again when the v18 card lands the served form; until then this text is true of 17.x.
- The reviewer corrected the dev's
Landing: Tier H. The PR stays draft for an authorized APPROVED review; in this same act the seat posts the final 速读 on the PR, adds
needs-user-decision, and requests review fromos-zhuangandhotlong.
Generated by Claude Code
- Shape.
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsClosed
completed— skills seat 1,session_01KTZmMfzVzjNvyaLyQ8mHvg, 2026-09-30T14:48Z.Delivered by PR #20811, MERGED 2026-09-30T14:43Z through the merge queue as
1d202453.- Content on
origin/main: inskills/objectstack-query, the six sites now state the engine's refusal (INVALID_FILTER/ 400) and the served two-step route.rules/filters.mdcarries## Relation Filters. - Landing record:
## Contract reviewPASS 5912934502 onb893787b, the landing head;- ACCEPT 5912952150;
- authorized approval by
os-zhuang(5367358255, aGOVERNED_APPROVERSaccount), who readied the PR and queued it.
- Follow-up:
- The stale mechanism paragraph in
content/docs/protocol/objectql/query-syntax.mdxis [finding] docs: query-syntax.mdx "Filtering Across Relationships" says SqlDriver compiles a nested relation object and emits a dotted key to Knex — both are refused at the engine before any driver since 4b4ee88f #20876 (docs lane). - Decision [Decision] v18:查询能否直接按关联记录的字段筛选(例:「客户行业 = 科技」的商机) #20802 was ruled A. Open PR feat(objectql): serve the nested-relation filter in where — lowered at the engine seam, the related object read as the caller, a loud cap, drivers untouched (#20802) #20872 (
Part of #20802, the engine half) names this card only in prose, as the skills seat's surface, "told after this lands". Once feat(objectql): serve the nested-relation filter in where — lowered at the engine seam, the related object read as the caller, a loud cap, drivers untouched (#20802) #20872 lands, teaching the served form is new work for a new card, ⛔ not a reopening of this delivered one. - Closed with
--no-skip-pr-referencedfor that reason: feat(objectql): serve the nested-relation filter in where — lowered at the engine seam, the related object read as the caller, a loud cap, drivers untouched (#20802) #20872 carries no closing keyword for this card.
- The stale mechanism paragraph in
- Closing:
Fixes #20782did not close this card at merge, so the seat closes it here.pm:dispatchedcomes off and the assignee is cleared.
Generated by Claude Code
- Content on
- added 2 commits that reference this issue
on Oct 7, 2026
Filing gate: ① a product defect with a named producer. Finding class (c), with a (b) half.
reach:the published skillskills/objectstack-query, which ships to customer projects and which AI authors read, atorigin/main, read by this seat:SKILL.md:185"Nested Relation Filters", with{ object: 'account', where: { contact: { profile: { verified: true } } } };SKILL.md:74(query.joins→ "a nested relation filter"),:332("to filter by a related record's column use a nested relation filter") and:341("Filter parent by child conditions | Nested relation filter");rules/filters.md:132"Nested Relation Filters", marked as a working form.The reading of the drivers is #20745's dev's (
os-dev-reporton #20745,out_of_scope_findings[0]), measured on memory, SQLite and PostgreSQL 16.Filed by the
domain:engineexecution seat 1 (session_01DEvba2nBuD4tWzfq8r8NFY,os-support-ai). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim. Reader: triage, then the seat that ownsskills/**(a governed surface, Tier H).What happens
An AI author who follows the skill writes
where: { account: { industry: 'tech' } }on a lookup.main: memory answers 200 with no rows; SQL answers 400 in the driver's words ([finding] a no-operator object under a lookup, master_detail or json field answers per driver: the declared nested-relation filter returns no rows on memory and a 400 on SQL, and a json object comparand deep-equals on memory and is refused on SQL #20745's table).INVALID_FILTER/ 400 on every driver. The refusal names the route that works: filter the related object, then$inon its ids (or$containsper id on a multi-valued relation).So the skill teaches a form that silently answers nothing on one driver and is refused on the rest, and that the engine is about to refuse everywhere. The code-side docs page (
content/docs/kernel/contracts/data-engine.mdx) is corrected in PR #20781; the skill is outside that PR becauseskills/**is governed.Scope for whoever takes it (⛔ not a ruling)
$inon its ids;$containsper id on a multi-valued relation. It says the engine refuses a nested object under a relation field.Dedupe
mcp__github__search_issues, repo-scoped, open and closed, in the act that filed this card:skills/objectstack-query/SKILL.mdtells an agent that engineaggregaterefusessearch/searchFields— false since PR #20487 #20500 (open) is a different false claim in the same skill (aggregateandsearch); the rest are closed skill-sweep and census cards. None is the nested relation form.Dedupe words:
objectstack-query skill nested relation filter·skill teaches refused filter form·filters.md nested relationGenerated by Claude Code