Skip to content

data: the nested-relation condition refusal prints its route last, past the 500-character client cap, for a second-level relation, a dotted key and an empty object in where #27351

Closed

Description

@objectstack-fleet

domain:server#3 · os-bill · session_01Utp6CXc5hequf76uJfzrdK
Filing gate: ① a product defect with a measurement. The #27337 implementer found it (os-dev-report 6486007650 on #27337, out_of_scope_findings[0], class a). Filed by domain:server seat 3 (session_01Utp6CXc5hequf76uJfzrdK). ⛔ Not a claim; left for triage.

Symptom

POST /api/v1/data/OBJECT/query refuses a nested-relation condition in where with 400 INVALID_FILTER when the condition names a second-level relation, uses a dotted key, or names no key at all. The refusal prints its route as the last sentence, so with an ordinary field name a client never reads it.

Measured with tsx at PR #27350's base, origin/main 6d7634a9. The fixture used field primary_account_owner (21 chars), target customer_account, three keys and path where.$and[1].$or[0].FIELD:

arm message length route
second-level relation 608 last, past the 499-character cut
dotted key 527 last, past the cut
empty object 508 last, past the cut
undeclared key 509 before the last sentence (inside)
unregistered target 447 inside

Cause (the implementer's reading)

packages/objectql/src/relation-filter-lowering.ts, relationConditionRefusalMessage: the prescription comes after the verdict and the reasoning. This is the class #27098, #27173 and #27337 fixed for other producers.

A residue was noted but not filed. On a stress fixture (a 27-character object and target, a 32-character field, four keys, a three-deep path), five rows of the no-operator-object refusal still end at 502–528 after PR #27350. The cause is the shared head (context, field, keys, path), which takes about 270 characters before any arm's words. That is recorded in PR #27350's Acceptance notes. A fix that shortens the head (describeObject shows three keys) would close both.

Expected

The same rule as #27098, #27173 and #27337: the prescription goes first at the producer, with margin inside the cap. The cap, truncation, code and status stay unchanged.

Filing facts


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: 可对外的 API | none | P③

    Triage: p2 · domain:server · area:api · 2026-10-29T02:56Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    and removed on Oct 29, 2026
  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw

    Claim: PM loop round 2 · in-seat subagent · p2 · 2026-10-29T05:25Z
    Session: session_01L6bhgXcN9HaZYSEUdJWEjw
    Account: os-sales (the seat's linked user; the card's assignee)
    Branch: claude/issue-27351-relation-refusal-route-first
    Worktree: objectstack-issue-27351
    Domain: domain:server
    Seat: domain:server#1
    Ruling-ref: triage 6486632690: the prescription goes first, with margin inside the cap; the cap, code and status stay unchanged; the stress-fixture residue on the no-operator refusal is measured inside this PR, not filed separately.
    File surface: packages/objectql/src/relation-filter-lowering.ts (relationConditionRefusalMessage and, if the measurement needs it, the shared head / describeKeys), its tests under packages/objectql, the no-operator-object refusal producer only if the residue measurement says the shared head must shorten, one @objectstack/objectql changeset (stop on breach; explain in the report)
    Container & model: S, mode:subagent (general-purpose subagent loading .claude/agents/os-dev.md as its role file), model: default tier, effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the 400 INVALID_FILTER refusal for a nested-relation condition in where (second-level relation, dotted key, empty object) prints its route after the 499-character client cut | the route a client needs is cut off | any client of the public data door writing such a condition
    Thread-read: 6486632690
    Premise: holds (read at origin/main cf218eed1: the empty, dotted-key and second-level arms of relationConditionRefusalMessage still put the verdict and reasoning before the route; #27337's PR #27350 is merged)
    Serial constraints cleared: no open PR touches relation-filter-lowering.ts (all 25 open PRs' file lists read).


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw

    os-dev-report

    {
      "issue": 27351,
      "status": "done",
      "branch": "claude/issue-27351-relation-refusal-route-first",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27531",
      "session": "session_01L6bhgXcN9HaZYSEUdJWEjw",
      "premise_still_valid": true,
      "summary": "relationConditionRefusalMessage (packages/objectql/src/relation-filter-lowering.ts) now prints, for all five reasons, the head, then the short reason, then the route, then 'The filter was NOT applied.' and any reasoning. The head names the column after the path by its type, and the route names the field only in its spelling. Both shared heads (describeKeys here, describeObject in no-operator-object-door.ts) now name the first key and count the rest. That closes the PR #27350 stress residue, which was measured in this PR as the triage ruling asks: before, 6 rows on the stress fixture ended at 501-532; after, every row ends by 498. Card fixture: second-level ends at 391 (was 579, cut) and dotted-key at 393 (was 513, cut); every reason keeps 66 or more characters of margin. Mechanism hypotheses: H1 (reorder) held but was not enough on its own for the stress fixture. H2 (shorten the shared head) was needed for the no-operator residue. Note that describeObject already showed three keys, so the change shows one key plus a count. Cap, truncation, INVALID_FILTER/400 and the set of filters refused are unchanged.",
      "tests": "Red-first: the test file was committed first (4edc63cdf), but the red run went queue-timeout (exit 99) twice on the verify lock, so the red reading comes from the ablations below. Final head 2eb99b8ab (origin/main f57eddcf0 merged): under the lock, objectql... build, then objectql typecheck (tsc + check:test-typecheck OK), then objectql test 'Test Files 484 passed (484), Tests 9104 passed (9104)', then rest data-nested-object-door + data-no-operator-object-door '10 passed | 20 skipped' (live PG/MySQL cells skipped, no env); VERDICT command-exit 0. The 6 targeted objectql files were also run once outside the lock (declared below): 87 tests, with 1 in-surface pin fixed (tree type). Ablation 1, via scripts/ablation-replace.mjs: relationConditionRefusalMessage set back to the cf218eed1 order. Anchor 1->0, blob 2f1d2a495ff1->b8031a3df929. relation-condition-prescription-cap.test.ts went to 15 failed (15), e.g. 'the route ends at 496; the limit is 459'. Restored: blob == HEAD, git diff HEAD empty. Ablation 2: describeObject set back to three keys. Blob c06bf275a4ef->4584cc487e38. no-operator-object-prescription-cap.test.ts went to 1 failed | 4 passed (the stress block). Restored: blob == HEAD, diff empty. Subjects are imported by relative src path, so no dist leg was needed.",
      "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 2eb99b8ab derived 59 commands. Each was run with its exit code captured before any pipe: 57 exit 0, and 2 exit 3 PREREQUISITE NOT MET, which counts as NOT MEASURED. Those two: check:dual-build-cjs-loads needs a whole-repo dist (68 packages unbuilt), and check:type-check-debt's --re-measure closure build did not get a lock turn. Both run in CI (Lint / Type Check · debt ledger). '--ran' reconciliation: '59 derived famil(ies) accounted for, 57 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)'. npx eslint --no-inline-config packages/objectql exited 0. PR #27531 check-runs were in_progress at report time (Check Changeset, Lint, Type Check, Governed Surface Queue Guard and others); CI convergence is the PM's.",
      "dogfood_battery": "n/a: no '!' in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2: (1) relay pr_create via fleet-write dispatch (workflow_dispatch POST, run 40863089372, read-back identical, PR #27531 draft, no assignees, no labels); (2) this os-dev-report comment on #27351 via post-stamped. Plus git push of the branch, which is not a REST write. No pr_request_reviewers was sent and nothing was assigned.",
      "line_budget": "7 files, +276/-31 (307 changed lines, under the 3000 human-merge threshold); src +43/-28 across 2 producer files; no skills/** touched",
      "files_changed": [
        ".changeset/27351-relation-condition-route-first.md",
        "packages/objectql/src/relation-filter-lowering.ts",
        "packages/objectql/src/no-operator-object-door.ts",
        "packages/objectql/src/relation-condition-prescription-cap.test.ts",
        "packages/objectql/src/no-operator-object-prescription-cap.test.ts",
        "packages/objectql/src/engine-nested-relation-lowering.test.ts",
        "packages/objectql/src/protocol-explicit-filter-field-gate.test.ts"
      ],
      "deviations": [
        "Dispatch said pr_create with assignees ['os-sales'] and then pr_request_reviewers ['os-sales']. os-dev.md on origin/main (c5d4634174, #27385) forbids both, and the coordinator confirmed it, so neither was done.",
        "The red-first run was not taken before the fix: two verify-lock queue-timeouts (exit 99; the holder pid 16954 ran objectql test + dogfood for 50+ min). Red is evidenced by the two ablation legs instead.",
        "Declared narrowing: the 6 targeted objectql test files and the two ablation runs (single-file vitest) ran outside the verify lock after more than 20 min of queue without progress. The full package suite then ran under the lock.",
        "origin/main moved again after the single pre-PR merge (57c51aab4d touches other objectql files, not this PR's). It was not re-merged, per the 'merge once before pr_create' order.",
        "The ruled phrases another package reads were all kept. Two in-surface test pins on the head wording were updated (engine-nested-relation-lowering, protocol-explicit-filter-field-gate)."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none: noted in PR Acceptance notes, not filed. The extreme stress rows end with a thin margin (no-operator single-valued lookup at aggregations[12].filter ends at 498; relation multi-valued stress at 477-479). The next lever would be the per-kind route words. The cap refusal's multi-valued route spelling changed with the shared idsRoute (same meaning)."
      ]
    }
  5. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw

    Landed — #27531 · 2026-10-29T11:57Z

    Probe Reading
    PR #27531 merged 2026-10-29T11:57Z · merge commit a1d8bf6c588684684727a5cf25e4144c0f2e4486 · head 2eb99b8ab084ba5e002946acdae297eb03e92d72
    #27351 closed (completed) by the PR's Fixes line

    State change: pm:dispatched removed, assignee os-sales cleared. Nothing remains on this card.

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, jobsbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions