fix(targets): ask whether a table is readable as a query, not with an untyped input #69

Merged
james.maes merged 1 commits from fix/target-read-asked-as-query into develop 2026-11-12 17:20:44 +00:00
Owner

What was seen

In a host application, every list screen of some tables showed a red error: "Resource not found POST /qqq/v1/qbit/collaboration/recents (HTTP 404)". The Next UI sends POST /recents with targetType: TABLE on every list screen (and with RECORD on every record screen) whenever the collaboration extension is installed. The person could read those tables: the list under the error was filled.

Cause

TargetResolver.canReadTable asked PermissionsHelper.hasTablePermission(new AbstractActionInput(), table, READ).

Core hands a table's custom permission checker the action input and does not say which permission is asked. A checker for a read-only table therefore tells a read from a write by the input's type: a query, a get or a count reads, anything else is refused. An untyped AbstractActionInput is none of those, so the checker refused, canReadTable answered false and TargetResolver.require threw QNotFoundException("Not found"): no recent and no favorite for that table, for anyone.

ParentRecordAccess in this repository already asks the same question with new GetInput(tableName).

Change

canReadTable asks with new QueryInput(table), the read it stands for. One line. Who may read a table does not change: the table's checker still decides, and a person it refuses is still answered "Not found".

Test

RecentsServiceTest.tableWithAnInputTypedPermissionCheckerIsATarget: a table whose checker reads the input's type; a person holding its permission records the table and one of its records as recent and lists both; a person without it is answered "Not found" for both. Before the change it failed on the first call with QNotFoundException: Not found.

./mvnw -B verify on this head: BUILD SUCCESS, 345 tests, 0 failures, 0 errors, 26 skipped (MySqlUploadJournalTest and PostgresUploadJournalTest, which run only with their system property set, as on develop), Checkstyle, the architecture guardrails and the JaCoCo gate passed.

Not in this change

  • The released line hosts consume is release/1.2 (1.2.0-RC.3). This fix needs a port there to reach an RC.
  • The UI shows a failed POST /recents as a page-level error although it is a background call nobody asked for; that belongs to the frontend.

🤖 Generated with Claude Code

## What was seen In a host application, every list screen of some tables showed a red error: "Resource not found POST /qqq/v1/qbit/collaboration/recents (HTTP 404)". The Next UI sends `POST /recents` with `targetType: TABLE` on every list screen (and with `RECORD` on every record screen) whenever the collaboration extension is installed. The person could read those tables: the list under the error was filled. ## Cause `TargetResolver.canReadTable` asked `PermissionsHelper.hasTablePermission(new AbstractActionInput(), table, READ)`. Core hands a table's custom permission checker the action input and does not say which permission is asked. A checker for a read-only table therefore tells a read from a write by the input's type: a query, a get or a count reads, anything else is refused. An untyped `AbstractActionInput` is none of those, so the checker refused, `canReadTable` answered false and `TargetResolver.require` threw `QNotFoundException("Not found")`: no recent and no favorite for that table, for anyone. `ParentRecordAccess` in this repository already asks the same question with `new GetInput(tableName)`. ## Change `canReadTable` asks with `new QueryInput(table)`, the read it stands for. One line. Who may read a table does not change: the table's checker still decides, and a person it refuses is still answered "Not found". ## Test `RecentsServiceTest.tableWithAnInputTypedPermissionCheckerIsATarget`: a table whose checker reads the input's type; a person holding its permission records the table and one of its records as recent and lists both; a person without it is answered "Not found" for both. Before the change it failed on the first call with `QNotFoundException: Not found`. `./mvnw -B verify` on this head: BUILD SUCCESS, 345 tests, 0 failures, 0 errors, 26 skipped (`MySqlUploadJournalTest` and `PostgresUploadJournalTest`, which run only with their system property set, as on `develop`), Checkstyle, the architecture guardrails and the JaCoCo gate passed. ## Not in this change - The released line hosts consume is `release/1.2` (1.2.0-RC.3). This fix needs a port there to reach an RC. - The UI shows a failed `POST /recents` as a page-level error although it is a background call nobody asked for; that belongs to the frontend. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
james.maes added 1 commit 2026-11-12 17:00:19 +00:00
fix(targets): ask whether a table is readable as a query, not with an untyped input
maven / build (push) Successful in 8m41s
build / maven (push) Successful in 8m46s
70f6184a7a
TargetResolver.canReadTable asked PermissionsHelper.hasTablePermission with a
bare AbstractActionInput. Core hands a table's custom permission checker the
action input and not the permission asked, so a checker that tells a read
from a write by the input's type (a query, a get or a count reads; anything
else is refused) refused the question. For such a table every recents and
favorites call answered "Not found" for people the checker lets read it:
the UI sends POST /recents on every list and record screen, so each of them
showed "Resource not found POST /qqq/v1/qbit/collaboration/recents (HTTP
404)".

The read is now asked with a QueryInput for the table, the read it stands
for; ParentRecordAccess already asks with a GetInput. Who may read a table
does not change: the checker still decides, and a person it refuses is
still answered "Not found" (RecentsServiceTest
.tableWithAnInputTypedPermissionCheckerIsATarget, which failed before with
QNotFoundException on the first call).

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
james.maes merged commit fbce2e5d6e into develop 2026-11-12 17:20:44 +00:00
Sign in to join this conversation.