You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
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/main6d7634a9. 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
reach: any client of the public data door writing a nested-relation condition in where that reaches past one relation hop, uses a dotted key, or is empty.
⛔ The same rule applies: the prescription goes first, with margin inside the cap, and the cap, code and status stay unchanged. The noted residue (the stress fixture on the no-operator refusal) is measured inside this PR, not filed separately.
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/maincf218eed1: 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).
{
"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)."
]
}
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 可对外的 API | none | P③
Triage: p2 ·
domain:server·area:api· 2026-10-29T02:56Z⛔ Not a claim.
relationConditionRefusalMessageprint the route last, past the 499-character client cut: a second-level relation, a dotted key and an empty object. This is the fourth producer of the class data: a 400 INVALID_FILTER prescription is cut at 500 characters, before the alternative it names, so a caller using a retired filter operator is not told what to write instead #27098, data: INVALID_FILTER refusals from the temporal-comparand and text-operator doors put their remedy past the 500-character client cap #27173 and data: the no-operator-object refusal's single-valued lookup route ends past the 500-character client cap, so its$inexample is cut on aggregations filter and having #27337 fixed, so it gets the same grade, p2.bug,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw
Claim: PM loop round 2 · in-seat subagent · p2 · 2026-10-29T05:25Z
Session:
session_01L6bhgXcN9HaZYSEUdJWEjwAccount:
os-sales(the seat's linked user; the card's assignee)Branch:
claude/issue-27351-relation-refusal-route-firstWorktree:
objectstack-issue-27351Domain:
domain:serverSeat:
domain:server#1Ruling-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(relationConditionRefusalMessageand, if the measurement needs it, the shared head /describeKeys), its tests underpackages/objectql, the no-operator-object refusal producer only if the residue measurement says the shared head must shorten, one@objectstack/objectqlchangeset (stop on breach; explain in the report)Container & model:
S,mode:subagent(general-purpose subagent loading.claude/agents/os-dev.mdas its role file),model: default tier,effort: mediumClause-②: no
Metadata-protocol: none
Responsibility: the
400 INVALID_FILTERrefusal for a nested-relation condition inwhere(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 conditionThread-read: 6486632690
Premise: holds (read at
origin/maincf218eed1: theempty,dotted-keyandsecond-levelarms ofrelationConditionRefusalMessagestill 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
objectstack-fleet commentedon Oct 29, 2026
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)." ] }objectstack-fleet commentedon Oct 29, 2026
domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw
Landed — #27531 · 2026-10-29T11:57Z
a1d8bf6c588684684727a5cf25e4144c0f2e4486· head2eb99b8ab084ba5e002946acdae297eb03e92d72FixeslineState change:
pm:dispatchedremoved, assigneeos-salescleared. Nothing remains on this card.