Skip to content

showcase my-work: the personal work queue authors filters: (plural) and no $filter ever reaches the wire — the grid lists every row #7750

Description

@huangyiirene

Symptom

The showcase's personal work queue is not personal. Open /_console/apps/showcase_app/page/showcase_my_work with the network recorder on, having first created a task owned by another user.

  • Expected $filter=[["owner_id","=","<current user>"]] on the grid's query.
  • Actual the wire carries no $filter at all, and the grid lists every row including the foreign-owned probe.

Reproduced twice with fresh logins. Contrast on the same run: active-projects' interfaceConfig.filterBy does reach the wire, so the page-level plumbing is fine.

Scope, stated precisely

Carried verbatim from the run so nobody over-escalates it:

Scope, stated precisely: this is not an authorization bypass. The unfiltered read is still RLS-constrained, so the caller sees only rows they may see (admin sees all because admin may see all). The defect is that a declared personal-scope filter silently never applies — and that a typo'd key is accepted without complaint. The second half is the more general problem.

The general half is filed separately as #7751.

Root cause — a key-name mismatch

examples/app-showcase/src/ui/pages/my-work.page.ts:52 authors the plural key:

{
  type: 'object-grid',
  properties: {
    objectName: 'showcase_task',
    columns: ['title', 'project', 'status', 'priority', 'due_date'],
    filters: [['owner_id', '=', '{current_user_id}']],
  },
},

objectui's ObjectGrid reads only schema.filter (packages/plugin-grid/src/ObjectGrid.tsx:472, lowered through toFilterNode) and the legacy schema.defaultFilters (line 657). schema.filters has zero read points in that file. filters appears exactly once in the whole showcase — this line; every other page uses filter.

Stale-premise check. Re-verified on objectstack origin/main (00e9196): line 52 still reads filters:. Re-verified on objectui origin/main (33c32bf): the renderer still reads only filter / defaultFilters, and schema.filters still has no reader — so this is not a stale-bundle artefact.

New since the run, and it changes the fix. objectui#4041 (commit 9154d9e, 2026-08-10) retired the plural spelling from the block's published vocabulary: packages/plugin-grid/src/index.tsx:117 now declares { name: 'filter', type: 'array', label: 'Filter' }, with gridFilterInputSpelling.test.tsx pinning that the declared name reaches $filter. At the time this page was authored, filters was what object-grid published, so the author was following the manifest — that excuse is now gone, and the one-word fix (filters: → filter:) lands on the declared, pinned, lowered path.

⚠️ Pin dependency for verification. 9154d9e landed after the console pin this run served (09987b680, 2026-08-09) and is present in the repo-root pin 6314e87f2. On a console built at the old pin, filter was not yet a reachable declared input, so the fix cannot be verified end-to-end until the vendored packages/console/dist is rebuilt — see #7752.

Reproduction

  1. Boot the showcase; sign in as user A and create a showcase_task owned by user B.
  2. Sign in as user A and open /_console/apps/showcase_app/page/showcase_my_work with the network recorder on.
  3. Inspect the grid's data request — there is no $filter parameter.
  4. Observe the foreign-owned task listed in the personal queue.

Source

Extracted from the QA run #7695 (framework 92f26f7, console 09987b680).

Activity

  1. self-assigned this
    on Aug 11, 2026
  2. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Claimed — domain:metadata seat.

    • Session: session_01BaTeBCjaoTDmDWFVmSY2jL
    • Branch: claude/issue-7750-my-work-filter-spelling
    • Worktree: ../objectstack-7750 (dedicated)
    • File surface: examples/app-showcase/src/ui/pages/my-work.page.ts — the fix is one word, filters: → filter:.

    The one-word fix is not the interesting part; the verification bound is, and the dispatch requires it stated honestly rather than papered over. objectui#4041 (9154d9e) retired the plural spelling and pinned that the declared filter reaches $filter — but it landed after the console pin this run served (09987b680), so on a console built at the old pin filter was not yet a reachable declared input. End-to-end verification therefore depends on the vendored packages/console/dist being rebuilt (#7752).

    So the dev checks which pin the worktree vendors: if it carries 9154d9e, drive the page and show $filter on the wire before and after. If it does not, ⛔ no fabricated end-to-end claim and ⛔ no rebuilding the vendored console to manufacture one — report the bound, pin what is checkable inside this repo, and reference #7752.

    Premise re-verification required before implementing: that line 52 still reads filters: on current main, and that filters really does appear exactly once across the showcase while every other page uses filter. If other sites exist, the card is under-scoped and that is worth knowing before one line is changed.

    Scope, carried verbatim so nobody over-escalates it: ⛔ this is not an authorization bypass — the unfiltered read is still RLS-constrained and the caller sees only rows they may see. The defect is that a declared personal-scope filter silently never applies. ⛔ Not to be relabelled as a security fix.

    ⛔ #7751 is out of scope — the general problem (a typo'd key accepted without complaint) is the larger card and is filed separately. Not folded in, and no validation added to the block schema here.


    Generated by Claude Code

  3. claude commented on Aug 11, 2026

    @claude
    Contributor
    {
      "issue": 7750,
      "status": "done",
      "branch": "claude/issue-7750-my-work-filter-spelling",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/7764",
      "premise_still_valid": true,
      "summary": "One word in examples/app-showcase/src/ui/pages/my-work.page.ts: the personal work queue's object-grid authored the plural `filters:` and now authors `filter:`, the key the block both publishes and reads. Premise confirmed on origin/main (8f1851e): line 52 still read `filters:`, and at the repo-root pin .objectui-sha=6314e87f2 the renderer declares `{ name: 'filter' }` (plugin-grid/src/index.tsx:117), reads schema.filter (ObjectGrid.tsx:472) plus legacy schema.defaultFilters (655-657), with ZERO occurrences of schema.filters. ONE SUB-CLAIM OF THE CARD IS IMPRECISE AND THE CORRECTION IS WORTH KEEPING: `filters` does NOT appear exactly once in the showcase — it appears at three other authored sites, all of which are legitimate DECLARED plural keys on different surfaces (ObjectNavItem.filters, spec/src/ui/app.zod.ts:421; react-tier ListView filters, spec/src/ui/react-blocks.ts:217). Read as 'exactly one object-grid authors the plural' the card holds and the fix is correctly one line; read literally it does not. The vocabulary genuinely splits by tier, which is why the typo was easy to make — context for #7751, not fixed here. Scope held: no validation added to the block schema, #7751 untouched, and this is NOT relabelled as an authorization bypass (the unfiltered read stayed RLS-constrained; the defect is that a declared personal-scope filter silently never applied).",
      "tests": "VERIFICATION BOUND, STATED HONESTLY — NO END-TO-END CLAIM. packages/console/dist/ is gitignored and ABSENT from a fresh clone, so this worktree vendors no console at all; producing one means rebuilding it, which the card prohibits as a way to manufacture an E2E claim. Tracked as #7752. No run in this repo can show $filter on the wire until that lands. WHAT WAS CHECKABLE WAS MEASURED, NOT ASSUMED: the card's pin-ancestry claim holds — GET /repos/objectstack-ai/objectui/compare/9154d9e...6314e87f2 returns status=ahead, behind_by=0, merge_base=9154d9e90f9c, so objectui#4041 IS an ancestor of the repo-root pin; the plugin-grid sources quoted above were read at that exact pin. || LOCAL RUNS: `pnpm --filter @objectstack/example-showcase test` -> 'Test Files 20 passed (20) / Tests 189 passed (189)'. `typecheck` (tsc --noEmit) -> clean, no output. Build closure `pnpm --filter '@objectstack/example-showcase^...' build` -> green (SUFFIX form = upstream dependencies, the correct direction for a build closure; no downstream consumer sweep was needed, this change exports nothing). Full package build per the ratchet's own named fix -> 'Tasks: 70 successful, 70 total'. || RATCHETS (the card flagged these): `pnpm check:type-check-debt` first REFUSED naming its own fix ('cannot run: 1 workspace dependency ... has no built type entry point'), so the workspace was built and it was re-run -> '33 ledger entries re-measured, 1786 raw tsc errors total, NONE ABOVE ITS RECORDED NUMBER'. NO LEDGER RAISED; the 10 'can be lowered' lines are pre-existing surplus, deliberately left untouched. `pnpm check:query-options-erasure` -> 'ratchet holds: 67 unswept non-test sites, none new; baseline key set verified against 8f1851e: no files added'. `node scripts/check-nul-bytes.mjs` -> OK (7124 files), plus a targeted control-byte self-scan of both changed files -> clean. || TEST_DEBT was avoided by DESIGN: the three pins were added to the EXISTING examples/app-showcase/test/my-work-visibility.test.ts, not a new file. || REVERSE VERIFICATION, direction predicted BEFORE running as ordinary 'Red' (the assertions read the authored key directly, so no ??-chain inversion applies) — prediction held exactly: reverting the page to origin/main turned ALL THREE new pins red while the four pre-existing tests stayed green: 'x authors `filter` (singular) -> expected {objectName: showcase_task, ...} to have property \"filter\"' / 'x carries no spelling the renderer drops -> `filters` is not the key object-grid reads' / 'x still scopes the queue to the signed-in user -> expected undefined to deeply equal [[owner_id, =, ...]]' / 'Test Files 1 failed (1)  Tests 3 failed | 4 passed (7)'. Fix restored via `git checkout HEAD -- <path>` (NEVER git stash), tree clean, suite green again at 189/189. || ONE ESLINT FINDING IS PRE-EXISTING, NOT MINE: '58:3 Definition for rule @typescript-eslint/no-implied-eval was not found' reproduces BYTE-IDENTICALLY on the unmodified origin/main copy of the same file, and line 58 sits above every line this PR adds. || CI GATE STATUS AT REPORT TIME, RECORDED HONESTLY: in_progress. On head 10eb592c, ESLint = in_progress and TypeScript Type Check = in_progress — the two gates the PM reads. Already green: Check Changeset (success — the skip-changeset label landed ahead of its re-read), Console Pin Freshness, ADR maintainer approval, Check PR Size, Check Documentation Links, 'No other open PR may claim the same issue'. Still running: Build Core, Test Core (1-3/3), Dogfood Regression Gate (1-3/3), Dogfood Verify CLI, Temporal Conformance. Per the draft-PR-time reporting contract I did NOT idle-poll CI.",
      "changeset": "NONE — skip-changeset label applied instead, and read back. @objectstack/example-showcase is private:true and absent from the `fixed` group in .changeset/config.json (verified: 0 occurrences), so a showcase-only change releases nothing. Same call #7568 made earlier today on this package. Label written as a UNION read-back, never a bare set; final read-back after the bots settled: ['size/s', 'tests', 'skip-changeset'].",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT FILED as a new issue, deliberately — it belongs to the already-open #7751 and filing discipline says comment on a hit rather than open a twin, while this card says #7751 is out of scope. Recorded here for the PM to route: the plural/singular split is real VOCABULARY drift across tiers, not just one author's typo. `object-grid` declares `filter` (singular), while `ObjectNavItem.filters` (spec/src/ui/app.zod.ts:421) and the react-tier `ListView.filters` (spec/src/ui/react-blocks.ts:217) both declare the plural — so an author moving between tiers on the same page is switching spelling with no signal. Worth noting for #7751: the spec ALREADY has the loud-rejection pattern this needs — the context-selector `optionsSource` (spec/src/ui/app.zod.ts:1066) is a strictObject carrying `aliases: { filters: 'filter', where: 'filter' }`, which rejects the typo by name with the fix in the message. SDUI blocks get none of it because `object-grid` is not in `ComponentPropsMap` at all and `packages/lint/src/validate-component-props.ts` SKIPS unregistered types by design (documented in its own header, lines 84-95) — that skip is the exact hole #7751 describes."
      ]
    }

    Generated by Claude Code

  4. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    ACCEPT — patch verified, PR #7764 flipped ready, auto-merge armed.

    The patch is what I asked for and slightly better. Rather than reword one shared string, you split the shared assertion into per-key { key, why } pairs so each spelling carries its own reason, and the new JSDoc states the distinction outright — filters is DEAD (zero read points, dropped before the wire), defaultFilters is ALIVE (the legacy path object-grid still reads and lowers to $filter, pinned absent because this page authors the current key, not because the legacy one is inert). The added warning is the part that will actually do work later: a future author debugging a filter bug must not read this test as licence to treat defaultFilters as a no-op nor reach for it as a workaround. Both failure directions, named.

    Confirmed the constraints held: the page fix is byte-identical, no assertion's truth value changed, and the diff is still the same two files.

    Your correction to the card's "exactly once" sub-claim stands as the record. filters appears at three other authored sites in the showcase, all legitimate declared plural keys on other surfaces (ObjectNavItem.filters, react-tier ListView.filters). The card is correctly scoped, but not for the reason it gave — read literally the claim is false, read as "exactly one object-grid authors the plural" it holds. Reporting that instead of quietly smoothing it over is what let it become useful downstream.

    And it was useful downstream. I routed the vocabulary-drift finding to #7751 rather than opening a twin, with all three of its claims verified against origin/main first. I also added the consequence you didn't state, which changes what closing #7751 costs: validate-component-props.ts's skip of unregistered types is deliberate and load-bearing — its own header says so, and turning it off would fail 87 legitimate nodes. So the hole is upstream of the lint rule, in SDUI block props not being knowable to this repo's linter, and the existing optionsSource alias machinery only helps once they are.

    The verification bound was the right call and I want it on record as such. packages/console/dist/ is gitignored and absent from a fresh clone, so no run in this repo can show $filter on the wire until #7752 lands. You measured what was checkable — the pin ancestry, via compare/9154d9e...6314e87f2 returning status: ahead, behind_by: 0 — and declined to manufacture the rest. A fabricated end-to-end claim here would have been worth less than no claim, because it would have retired the card while leaving the actual gap unmeasured.

    Changeset skip independently re-verified: @objectstack/example-showcase is private: true and appears zero times in the fixed group.

    Close-out on merge: archive session_01BaTeBCjaoTDmDWFVmSY2jL.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingpm:queue

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions