Skip to content

[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

@objectstack-fleet

Filing gate: ① a product defect with a named producer. Finding class (c), with a (b) half. reach: the published skill skills/objectstack-query, which ships to customer projects and which AI authors read, at origin/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-report on #20745, out_of_scope_findings[0]), measured on memory, SQLite and PostgreSQL 16.

Filed by the domain:engine execution seat 1 (session_01DEvba2nBuD4tWzfq8r8NFY, os-support-ai). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim. Reader: triage, then the seat that owns skills/** (a governed surface, Tier H).

What happens

An AI author who follows the skill writes where: { account: { industry: 'tech' } } on a lookup.

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 because skills/** is governed.

Scope for whoever takes it (⛔ not a ruling)

Dedupe

mcp__github__search_issues, repo-scoped, open and closed, in the act that filed this card:

Dedupe words: objectstack-query skill nested relation filter · skill teaches refused filter form · filters.md nested relation


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: 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 lands

    Triage 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:

    AI authors copy it.

    Direction.

  2. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    documentationImprovements or additions to documentation
    and removed on Sep 30, 2026
  3. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01KTZmMfzVzjNvyaLyQ8mHvg
    Account: os-warren (the seat's linked user as GET /user answers 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 row query.joins (:76), the rules index line (:88, "nested relations"), the "Nested Relation Filters" subsection (:187-:194), the search never traverses sentence (:334), and the Cross-Object table row (:343). skills/objectstack-query/rules/filters.md, the "Nested Relation Filters" section (:132 to the section's end, before "Common Mistakes"). scripts/check-skills-token-ratchet.mjs only if a ceiling row is lowered. The rest of skills/** is read for the census (no other hit on origin/main 96e72447). content/docs/** is read only, ⛔ not edited: PR #20781 already corrected data-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:

    Readings at 2026-09-30T07:36Z on origin/main 96e72447:

    • 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

  4. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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

  5. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — 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-②: no at a line start.
      • Files: skills/objectstack-query/SKILL.md (+9 / −11), rules/filters.md (+14 / −51), and one row of scripts/role-word-baseline.json.
      • Tier H (skills/**).
    • 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 $contains per id on a multiple: true lookup.
      • The parent-by-child row is split into its two directions.
      • Nothing anticipates the v18 decision.
    • Contract review: PASS 5912934502 on this head. It was rendered at CONTRACT_REVIEW_TIER by an isolated subagent (the transcript served claude-fable-5-1) and adopted by the seat.
      • No door applies a default row limit to engine.find. The spec's limit has no default, and the SQL and memory drivers apply LIMIT only 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.
    • Budgets.
      • The package goes from 1184 to 1145 lines, net −39.
      • SKILL.md is at 4055 / 5552 tokens and rules/filters.md at 2149 / 2149. No ceiling moved.
    • Changeset. skip-changeset on the PR; assignee os-warren.
    • CI on b893787b. 24 success and 11 path-filtered skipped. Every required context is success or path-skipped.
    • Out of scope.

    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 from os-zhuang and hotlong.


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Closed 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.


    Generated by Claude Code

  7. added 2 commits that reference this issue on Oct 7, 2026
    1d20245
    ca5408c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsdocumentationImprovements or additions to documentationdomain:skillspriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions