Skip to content

fix(agent): apply the scope of collections reached through a relation - #421

Open
ShohanRahman wants to merge 1 commit into
mainfrom
fix/prd-907-apply-related-collection-scopes
Open

ShohanRahman wants to merge 1 commit into
mainfrom
fix/prd-907-apply-related-collection-scopes

Conversation

@ShohanRahman

@ShohanRahman ShohanRahman commented Oct 30, 2026 •

Copy link
Copy Markdown

fixes PRD-907

This is the agent-ruby counterpart of ForestAdmin/agent-nodejs#2051. Read routes apply the scope of every collection they reach through a to-one relation, as well as the scope of the collection they serve. A related record outside its collection's scope is served empty, and the record it hangs off stays in the response.

What changes

  1. show / list / list_related. Permissions#relation_scopes fetches the scope of each collection the projection reaches through a to-one relation. The columns it reads are added to the query, each related record is checked with ConditionTree#match, and a record outside its scope is served as nil. The added columns are stripped before serialization.
  2. csv / csv_related. The same redaction runs on every batch of the export stream, so an out-of-scope related record's cells are empty.
  3. Leaderboard chart. The aggregated collection's own scope is intersected into the filter. For a many-to-many, that is the through collection's scope, plus the foreign collection's scope nested through the target relation.
  4. Rows and statuses are unchanged. The listed rows and the show status are the same as before. An out-of-scope related record looks the same as an unassigned relation.

Notes for review

  • Polymorphic many-to-one relations have no single foreign collection, so relation_scopes skips them.
  • Scopes are evaluated in memory, so null handling follows the toolkit matcher, not the datasource's SQL.
  • spec_helper's shared context with readable related collections stubs relation_scopes with an empty RelationScopes, so the route specs keep their permission doubles. spec/lib/forest_admin_agent/security/related_collection_scopes_spec.rb runs the real path.

Tests

  • 8 new examples cover the redaction and the leaderboard filters.
  • The full forest_admin_agent suite passes: 1,435 examples. Rubocop is clean.

Squash message

fix(agent): apply the scope of collections reached through a relation

Read routes fetch the scope of every collection a projection reaches
through a to-one relation and serve a related record outside it as nil,
keeping the record it hangs off. The leaderboard intersects the scope of
the aggregated collection, and for a many-to-many the foreign collection's
through the target relation, into its filter.

Refs: PRD-907

Definition of Done

General

  • Write an explicit title for the Pull Request, following Conventional Commits specification
  • Test manually the implemented changes
  • Validate the code quality (indentation, syntax, style, simplicity, readability)

Security

  • Consider the security impact of the changes made

🤖 Generated with Claude Code

Read routes only fetched the scope of the collection they serve, so the
columns a projection pulls from to-one relations ignored the scope set on
those related collections, and a leaderboard aggregated its related
collection under the root scope only.

show, list, csv, list_related and csv_related now fetch the scope of every
collection the projection reaches, and empty a related record that falls
outside it, keeping the root row. The leaderboard intersects the
aggregated collection's scope (and, for a many-to-many, the foreign
collection's through the target relation) into its filter.

Refs: PRD-907

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 30, 2026

Copy link
Copy Markdown

PRD-907

@qltysh

qltysh Bot commented Oct 30, 2026

Copy link
Copy Markdown

1 new issue

Tool Category Rule Count
qlty Structure Function with high complexity (count = 8): collect_relation_scopes 1

nested = collect_relation_scopes(foreign_collection, sub_projection, path)

scope ? [Utils::RelationScopes::RelationScope.new(path, foreign_collection, scope), *nested] : nested
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 8): collect_relation_scopes [qlty:function-complexity]

@qltysh

qltysh Bot commented Oct 30, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (8)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
...est_admin_agent/lib/forest_admin_agent/services/permissions.rb100.0%
Coverage rating: A Coverage rating: A
...st_admin_agent/lib/forest_admin_agent/routes/resources/list.rb100.0%
Coverage rating: A Coverage rating: A
...st_admin_agent/lib/forest_admin_agent/routes/resources/show.rb100.0%
Coverage rating: A Coverage rating: A
...ib/forest_admin_agent/routes/resources/related/list_related.rb100.0%
Coverage rating: A Coverage rating: A
...est_admin_agent/lib/forest_admin_agent/routes/resources/csv.rb66.7%45
Coverage rating: A Coverage rating: A
...est_admin_agent/lib/forest_admin_agent/routes/charts/charts.rb100.0%
Coverage rating: A Coverage rating: A
...lib/forest_admin_agent/routes/resources/related/csv_related.rb100.0%
New file Coverage rating: A
...st_admin_agent/lib/forest_admin_agent/utils/relation_scopes.rb100.0%
Total98.2%
🤖 Increase coverage with AI coding...
In the `fix/prd-907-apply-related-collection-scopes` branch, add test coverage for this new code:

- `packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/csv.rb` -- Line 45

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

@scopes.filter_map do |relation_scope|
related = ForestAdminDatasourceToolkit::Utils::Record.field_value(stringified, relation_scope.path)

relation_scope.path if related && !relation_scope.scope.match(related, relation_scope.collection, timezone)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High utils/relation_scopes.rb:47

A related record with a NULL value for a scoped comparison field crashes the read or export request instead of being redacted; for example, balance > 0 reaches nil > 0 in scope.match. The related check only excludes an absent relation, so handle null comparison operands before invoking the matcher.

Also found in 1 other location(s)

packages/forest_admin_agent/lib/forest_admin_agent/services/permissions.rb:299

The scopes collected here expose a crash in the unchanged toolkit matcher (components/query/condition_tree/nodes/condition_tree_leaf.rb, match): its less_than/greater_than branches compare field_value directly, without handling nulls. For example, a related account scoped by balance &gt; 0 with a null balance now reaches nil &gt; 0 via RelationScopes#redact. The latter only guards against the entire related record being absent, so one such account crashes the whole list/show request or aborts the CSV export rather than redacting the account. Handle nullable comparison operands before using this matcher for related scopes.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/forest_admin_agent/lib/forest_admin_agent/utils/relation_scopes.rb around line 47:

A related record with a `NULL` value for a scoped comparison field crashes the read or export request instead of being redacted; for example, `balance > 0` reaches `nil > 0` in `scope.match`. The `related` check only excludes an absent relation, so handle null comparison operands before invoking the matcher.

Also found in 1 other location(s):
- packages/forest_admin_agent/lib/forest_admin_agent/services/permissions.rb:299 -- The scopes collected here expose a crash in the unchanged toolkit matcher (`components/query/condition_tree/nodes/condition_tree_leaf.rb`, `match`): its `less_than`/`greater_than` branches compare `field_value` directly, without handling nulls. For example, a related account scoped by `balance > 0` with a null balance now reaches `nil > 0` via `RelationScopes#redact`. The latter only guards against the entire related record being absent, so one such account crashes the whole list/show request or aborts the CSV export rather than redacting the account. Handle nullable comparison operands before using this matcher for related scopes.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant