Skip to content

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

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
Contributor

fixes PRD-907

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. get / list / list-related. The scope of each collection the projection reaches through a to-one relation is fetched (AuthorizationService.getRelationScopes). The columns it reads are added to the query, the scope is evaluated on each related record (ConditionTree.match), and a record outside it is served as null. The added columns are stripped before serialization.
  2. csv / csv-related. The same redaction runs on every page of the export, 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, the count and the get-one status are the same as before. A card whose account is out of scope is listed with account: null, the same way as a card with no account.

Notes for review

  • Scopes are evaluated in memory with ConditionTree.match, so null handling follows the toolkit's matcher, not the datasource's SQL. For example, region != 'EU' keeps a holder whose region is NULL.
  • Scopes apply whatever skipRelationReadPermissions says, because that option covers read permissions, not scopes.
  • The agent-ruby counterpart is fix(agent): apply the scope of collections reached through a relation agent-ruby#421.

Tests

  • test/security/related-collection-scopes.test.ts drives each route through the real AuthorizationService.
  • test/utils/relation-scopes.test.ts covers the redaction itself: nested paths, a missing relation, and a deeper scope evaluated before its parent is emptied.
  • The full agent suite passes: 97 suites, 2,020 tests.

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 null,
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.

get, 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 many parameters (count = 4): collectRelationScopes 1

@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
packages/agent/src/services/authorization/authorization.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent/src/routes/access/list-related.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent/src/routes/access/get.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent/src/routes/access/csv.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent/src/routes/access/chart.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent/src/routes/access/csv-related.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent/src/routes/access/list.ts100.0%
New file Coverage rating: A
packages/agent/src/utils/relation-scopes.ts100.0%
Total100.0%
🚦 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.

This branch has not been deployed

No deployments
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