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
fix(table): reject empty filter groups and bind cursors to their sort
Bugbot round 2 on PR #6067, both verified:
Empty groups (High). `{all: []}` fails the strict predicate branch (.min(1))
but slips the dual union via the legacy branch — an empty ARRAY inside a
non-empty OBJECT — then downgraded to `{$and: []}`, which compiles to no WHERE
clause: a run/cancel/delete scope silently widened to every row.
validatePredicateShape now mirrors the contract's .min(1), which closes it at
every dual-grammar boundary at once (toLegacyFilter, resolveBulkFilter, the
GET native path).
Cursor↔sort binding (Medium). CURSOR_SORT_CONFLICT only fired for keyset
cursors; offset cursors — the shape sorted views actually emit — carried no
record of their ordering, so one minted under sort A replayed under sort B (or
none) silently paged the wrong sequence. Offset cursors are now stamped with a
canonical fingerprint of their sort at mint (queryRows), and a shared
assertCursorSortBinding enforces the match at all three consumers (both query
routes and the copilot executor), replacing the three hand-rolled keyset-only
checks. Keyset/compound cursors stay default-order-only by construction. The
OpenAPI cursor wording now states the binding rather than overclaiming.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M563cbFy2S74GvSDf2C3R
Copy file name to clipboardExpand all lines: apps/docs/openapi-v2-tables.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -60,7 +60,7 @@
60
60
"post": {
61
61
"operationId": "v2QueryTableRows",
62
62
"summary": "Query Rows",
63
-
"description": "Query rows with a typed predicate filter, an ordered sort spec, and opaque cursor pagination. Row `data` is keyed by column NAME; `select` cells return option names, and filter operands on select columns accept option names (resolved case-insensitively).\n\n**Pagination contract:** page by passing the previous response's `nextCursor` back as `cursor`, and stop only when it is `null` — a page may return fewer than `limit` rows and still have more behind it, so page fullness is never a termination signal. A cursor encodes a position in the default row order and cannot be combined with `sort` (400 `CURSOR_SORT_CONFLICT`). `totalCount` is computed on the first page only (requests with a `cursor` return `totalCount: null`).",
63
+
"description": "Query rows with a typed predicate filter, an ordered sort spec, and opaque cursor pagination. Row `data` is keyed by column NAME; `select` cells return option names, and filter operands on select columns accept option names (resolved case-insensitively).\n\n**Pagination contract:** page by passing the previous response's `nextCursor` back as `cursor`, and stop only when it is `null` — a page may return fewer than `limit` rows and still have more behind it, so page fullness is never a termination signal. A cursor is bound to the exact query shape it was minted under: keyset cursors to the default row order, offset cursors (sorted views) to that sort. Replaying one under a different `sort` returns 400 `CURSOR_SORT_CONFLICT`. `totalCount` is computed on the first page only (requests with a `cursor` return `totalCount: null`).",
0 commit comments