Skip to content

Conversation

@KevLehman
Copy link
Member

@KevLehman KevLehman commented Oct 8, 2025

Proposed changes (including videos or screenshots)

Issue(s)

https://rocketchat.atlassian.net/browse/ABAC-53

Steps to test or reproduce

Further comments

Why only when new attributes?

Suppose you have a room expecting [1,2,3] and a user with values [1,2,3,5]

If you then change the room expectations to be [1,2], the same user will still be able to access the room, cause the room attributes became "less strict".

However, if you instead change it to be [1,2,3,4], then we have to re-evaluate, as the room is now "more strict". This would give the result that the user cannot access the room, as attribute 4 is different.

Same happens if the attributes are changed to be [1,6]. The attribute list was reduced, but a new attribute was added, and thus we have to evaluate again.

(For users, not yet implemented, is the exact inverse of this logic)

Summary by CodeRabbit

  • New Features

    • Added ABAC-based automatic user removal from rooms with system message notification.
    • Support for custom system messages during user removal operations.
    • Automatic enforcement of attribute-based access control policies with non-compliance detection.
  • Documentation

    • Added new system message type registration for ABAC removal events with localized messaging support.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Oct 8, 2025

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Oct 8, 2025

⚠️ No Changeset found

Latest commit: e4bb6e7

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@KevLehman KevLehman changed the base branch from develop to feat/abac October 8, 2025 18:48
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 8, 2025

Walkthrough

This pull request implements automatic user removal from ABAC-protected rooms when new attributes are added. It extends the removeUserFromRoom function with optional controls, introduces lifecycle hooks to detect attribute changes, performs compliance reevaluation with concurrency control, and adds corresponding message types and localization.

Changes

Cohort / File(s) Summary
ABAC Core Implementation
ee/packages/abac/src/index.ts
Added concurrency control (p-limit), logging, and functional lifecycle hook. Implements onRoomAttributesChanged to detect attribute additions, compute non-compliant conditions, and remove affected users. Introduced didAttributesChange helper to gate triggering. Added default export.
ABAC Integration Tests
ee/packages/abac/src/user-auto-removal.spec.ts
New integration test suite validating onRoomAttributesChanged workflows with in-memory MongoDB, covering attribute additions, removals, multi-attribute AND semantics, idempotency, edge cases, error handling, and large population scenarios.
ABAC Unit Tests
ee/packages/abac/src/index.spec.ts
Updated expectations for hook invocation: now triggers on additions with post-operation attributes; removals do not trigger. Aligned tests across setRoomAbacAttributes, updateRoomAbacAttributeValues, replaceRoomAbacAttributeByKey, and addRoomAbacAttributeByKey.
ABAC Package Configuration
ee/packages/abac/package.json
Updated description to "Rocket.Chat" (from "Enterprise"), added dependencies (@rocket.chat/logger, p-limit, mongodb-memory-server), removed @rocket.chat/string-helpers, and removed engines.node and license fields.
Room Service Layer
apps/meteor/server/services/room/service.ts
Added MessageTypesValues import. Extended removeUserFromRoom signature to make byUser optional and add skipAppPreEvents and customSystemMessage options.
User Removal Core Logic
apps/meteor/app/lib/server/functions/removeUserFromRoom.ts
Extended options with skipAppPreEvents and customSystemMessage. Guards pre-leave app event invocation behind skip flag. Supports custom system messages; uses provided message or derives from byUser data.
Room Export Utility
apps/meteor/server/lib/dataExport/exportRoomMessagesToFile.ts
Added switch case for 'abac-removed-user-from-room' message type with localized text mapping.
Message Type Definitions
packages/core-typings/src/IMessage/IMessage.ts, packages/apps-engine/src/definition/messages/MessageType.ts
Added 'abac-removed-user-from-room' to message type union and array across core typings and apps-engine.
Service Contract
packages/core-services/src/types/IRoomService.ts
Added MessageTypesValues import. Updated removeUserFromRoom signature matching service implementation: byUser optional, plus new skipAppPreEvents and customSystemMessage options.
Localization & Registration
packages/i18n/src/locales/en.i18n.json, packages/message-types/src/registrations/common.ts
Added i18n entry abac_removed_user_from_the_room ("was removed by ABAC"). Registered new message type with system message id and text key.

Sequence Diagram

sequenceDiagram
    actor Admin
    participant RoomService
    participant AbacService
    participant Room DB
    participant Users

    Admin->>RoomService: setRoomAbacAttributes(roomId, newAttrs)
    RoomService->>Room DB: Update ABAC attributes
    Room DB-->>RoomService: Updated room
    RoomService->>AbacService: onRoomAttributesChanged(room, newAttrs)
    
    rect rgb(200, 220, 240)
    Note over AbacService: Detect changes
    AbacService->>AbacService: didAttributesChange(current, next)
    end
    
    alt Attributes added
        AbacService->>AbacService: buildNonCompliantConditions(room, newAttrs)
        AbacService->>Users: Query ABAC definitions & user attributes
        Users-->>AbacService: User compliance results
        
        rect rgb(240, 200, 200)
        Note over AbacService: Remove non-compliant users
        end
        
        par Concurrent removals (p-limit: 20)
            AbacService->>RoomService: removeUserFromRoom(roomId, user, {skipAppPreEvents: true, customSystemMessage: 'abac-removed-user-from-room'})
            RoomService->>Room DB: Remove user subscription
            Room DB-->>RoomService: Success
        end
        
        AbacService-->>RoomService: Removal complete
    else No attribute changes
        Note over AbacService: No-op, return early
    end
    
    RoomService-->>Admin: Success
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Rationale: The changes introduce new ABAC lifecycle logic with compliance reevaluation, concurrency control, and user removal orchestration across multiple files. While changes are cohesive and focused on ABAC attribute management, reviewers must evaluate: (1) correctness of compliance detection and removal logic, (2) concurrency handling and race conditions, (3) integration with existing room/user removal flows, (4) test coverage adequacy, and (5) consistency across service layer contracts. The addition of comprehensive integration tests and clear separation of concerns moderates complexity.

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • dougfabris
  • sampaiodiego

Poem

🐰 When ABAC attributes bloom and grow,
Non-compliant whiskers must now go!
With p-limit grace and logging light,
We hop through rooms, removing right.
Concurrency hops, compliance reaps—
The warren's rules, we help enforce and keep! 🎯

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: Remove users from room when new attributes are added to the room" directly describes the primary objective of the pull request. The changes comprehensively implement logic to re-evaluate and remove users from rooms when ABAC attributes become more restrictive through the addition of new attributes, which is the core feature being delivered. The title is concise, specific, and accurately reflects the main change without unnecessary verbosity or vague language.
Linked Issues Check ✅ Passed The pull request fully addresses all coding objectives from ABAC-53. The implementation includes: enhanced onRoomAttributesChanged method that accepts room context and executes user removals when attributes change [index.ts]; a didAttributesChange helper that differentiates between attribute additions requiring re-evaluation versus less-strict changes that don't [index.ts]; comprehensive integration tests validating re-evaluation logic, idempotency, and handling of edge cases [user-auto-removal.spec.ts]; and new message type and localization support for the removal event [MessageType.ts, IMessage.ts, en.i18n.json]. All requirements to detect new attributes, re-evaluate member access, and remove non-compliant users are successfully implemented with proper test coverage.
Out of Scope Changes Check ✅ Passed All changes are either directly required to implement ABAC-53 or represent reasonable supporting modifications. Core feature implementation includes the enhanced ABAC service with attribute change detection and user removal logic [index.ts, user-auto-removal.spec.ts]; updated service interfaces and implementations [IRoomService.ts, room/service.ts, removeUserFromRoom.ts]; and new message type infrastructure [MessageType.ts, IMessage.ts, en.i18n.json, message-types/common.ts]. Package.json modifications include necessary dependencies (p-limit for concurrency control, logger for improved observability, mongodb-memory-server for integration testing) that directly support the feature implementation. These additions and the description adjustment are justified by the implementation needs.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/on-attribute-changed-hooks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Oct 8, 2025

Codecov Report

❌ Patch coverage is 92.00000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.16%. Comparing base (5216368) to head (e4bb6e7).
⚠️ Report is 1 commits behind head on feat/abac.

Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##           feat/abac   #37172      +/-   ##
=============================================
+ Coverage      70.09%   70.16%   +0.07%     
=============================================
  Files           3035     3035              
  Lines         104228   104266      +38     
  Branches       18487    18499      +12     
=============================================
+ Hits           73055    73155     +100     
+ Misses         29228    29172      -56     
+ Partials        1945     1939       -6     
Flag Coverage Δ
e2e 57.42% <ø> (+0.07%) ⬆️
unit 71.68% <92.00%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@KevLehman KevLehman force-pushed the feat/on-attribute-changed-hooks branch from 9d0dfe9 to 2e1e8e3 Compare October 10, 2025 18:12
@KevLehman KevLehman marked this pull request as ready for review October 13, 2025 15:37
@KevLehman KevLehman requested review from a team as code owners October 13, 2025 15:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (7)
packages/apps-engine/src/definition/messages/MessageType.ts (1)

143-145: New message type looks good; minor comment style nit

Use a proper JSDoc block and “ABAC” capitalization for clarity.

-// ** Sent when a user was removed from an abac room */
+/** Sent when a user was removed due to ABAC policy */
ee/packages/abac/src/index.ts (2)

435-446: Tighten Users.find projection for performance

Only _id and username are required downstream. Avoid fetching entire user docs.

-const cursor = Users.find(query, { projection: { __rooms: 0 } });
+const cursor = Users.find(query, { projection: { _id: 1, username: 1 } });

405-410: Minor: fix comment typo

“Should w remove all members?” → “Should we remove all members?”

ee/packages/abac/src/user-auto-removal.spec.ts (4)

9-18: Capture and assert Room.removeUserFromRoom options (use jest.fn).

The PR adds skipAppPreEvents/customSystemMessage. Make the mock a jest.fn to capture the 3rd param and allow assertions that ABAC removals set those flags.

Apply this diff to the mock:

-jest.mock('@rocket.chat/core-services', () => ({
-	ServiceClass: class {},
-	Room: {
-		// Mimic the DB side-effects of removing a user from a room (no apps/system messages)
-		removeUserFromRoom: async (roomId: string, user: any) => {
-			const { Subscriptions } = await import('@rocket.chat/models');
-			await Subscriptions.removeByRoomIdAndUserId(roomId, user._id);
-		},
-	},
-}));
+jest.mock('@rocket.chat/core-services', () => {
+	const removeUserFromRoom = jest.fn(async (roomId: string, user: any, options?: any) => {
+		const { Subscriptions } = await import('@rocket.chat/models');
+		await Subscriptions.removeByRoomIdAndUserId(roomId, user._id);
+		// options captured implicitly by jest.fn for assertions
+	});
+	return {
+		ServiceClass: class {},
+		Room: { removeUserFromRoom },
+	};
+});

Then, in one of the tests after calling the service, assert the options (example):

const { Room } = await import('@rocket.chat/core-services');
expect(Room.removeUserFromRoom).toHaveBeenCalledWith(
  expect.any(String),
  expect.objectContaining({ _id: expect.any(String) }),
  expect.objectContaining({ skipAppPreEvents: true, customSystemMessage: 'abac-removed-user-from-room' }),
);

31-43: Reuse the shared AbacService instance to add definitions.

Avoid spinning extra AbacService instances; reuse the spied instance so logs/behavior are consistent and test setup is lighter.

-const insertDefinitions = async (defs: { key: string; values: string[] }[]) => {
-	const svc = new AbacService();
+const insertDefinitions = async (defs: { key: string; values: string[] }[]) => {
 	await Promise.all(
 		defs.map((def) =>
-			svc.addAbacAttribute({ key: def.key, values: def.values }).catch((e: any) => {
+			service.addAbacAttribute({ key: def.key, values: def.values }).catch((e: any) => {
 				if (e instanceof Error && e.message === 'error-duplicate-attribute-key') {
 					return;
 				}
 				throw e;
 			}),
 		),
 	);
 };

116-120: Remove shadowed debugSpy; use the outer spy instance.

Local const debugSpy redefines the outer spy and can confuse readers. Rely on the outer jest.SpyInstance everywhere.

-const debugSpy = (service as any).logger.debug as jest.Mock;

Repeat this removal in similar tests; the outer debugSpy already exposes .mock.calls and .mockClear().


120-128: DRY up log extraction for reevaluation events.

The mapping/filtering repeats. Extract a tiny helper for readability.

const getReevalCalls = () =>
  debugSpy.mock.calls.map((c) => c[0]).filter((a) => a && a.msg === 'Re-evaluating room subscriptions');

Then use const evaluationCalls = getReevalCalls(); in tests.

Also applies to: 150-159, 173-181, 199-206, 260-271, 284-315, 330-335, 351-359, 390-396

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c1b3cc1 and a5119bd.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (12)
  • apps/meteor/app/lib/server/functions/removeUserFromRoom.ts (3 hunks)
  • apps/meteor/server/lib/dataExport/exportRoomMessagesToFile.ts (1 hunks)
  • apps/meteor/server/services/room/service.ts (2 hunks)
  • ee/packages/abac/package.json (1 hunks)
  • ee/packages/abac/src/index.spec.ts (6 hunks)
  • ee/packages/abac/src/index.ts (10 hunks)
  • ee/packages/abac/src/user-auto-removal.spec.ts (1 hunks)
  • packages/apps-engine/src/definition/messages/MessageType.ts (1 hunks)
  • packages/core-services/src/types/IRoomService.ts (2 hunks)
  • packages/core-typings/src/IMessage/IMessage.ts (1 hunks)
  • packages/i18n/src/locales/en.i18n.json (1 hunks)
  • packages/message-types/src/registrations/common.ts (1 hunks)
🔇 Additional comments (8)
packages/core-typings/src/IMessage/IMessage.ts (1)

107-107: LGTM! New ABAC message type follows naming conventions.

The new message type literal 'abac-removed-user-from-room' is correctly added to the MessageTypes array and follows the established kebab-case naming convention consistent with other system messages like 'removed-user-from-team'.

packages/message-types/src/registrations/common.ts (1)

204-208: Verify username parameter is correctly passed to the translation function.

The registration doesn't pass the username to the translation function, but similar removal messages (like 'removed-user-from-team' at line 43) extract the username from message.msg and pass it as a parameter. The AI summary mentions the i18n key 'abac_removed__username__from_the_room' which suggests a username placeholder is expected.

If the localization string includes a {username} or {user_removed} placeholder, apply this diff:

 instance.registerType({
   id: 'abac-removed-user-from-room',
   system: true,
-  text: (t) => t('abac_removed_user_from_the_room'),
+  text: (t, message) => t('abac_removed_user_from_the_room', { username: message.msg }),
 });

Run the following script to verify the i18n key format:

ee/packages/abac/src/index.spec.ts (1)

416-426: Tests capture the “re-evaluate on additions only” policy well

Good coverage for new/added values vs removals and replace/add flows.

Also applies to: 428-439, 441-452, 529-534, 536-542, 571-581

ee/packages/abac/src/index.ts (1)

197-219: didAttributesChange logic is clear and matches policy

Returns true only on key/value additions; good guard to avoid unnecessary removals.

apps/meteor/server/lib/dataExport/exportRoomMessagesToFile.ts (1)

158-160: ABAC export text: verify i18n key and consider italic formatting

  • Confirm the i18n key used here matches the registration/locales (some files reference abac_removed__username__from_the_room vs abac_removed_user_from_the_room).
  • Consider adding this message type to italicTypes for consistent HTML export styling like 'ru' and 'ul'.

Run to verify key consistency across registration and locales:

Refactor (outside the selected lines) to italicize the ABAC message in HTML export:

-const italicTypes: IMessage['t'][] = ['uj', 'ul', 'au', 'r', 'ru', 'wm', 'livechat-close'];
+const italicTypes: IMessage['t'][] = ['uj', 'ul', 'au', 'r', 'ru', 'wm', 'livechat-close', 'abac-removed-user-from-room'];
packages/core-services/src/types/IRoomService.ts (1)

44-48: Public typings update LGTM; ensure implementors align

The options surface looks right. Please verify all implementors use byUser?: Pick<IUser, '_id' | 'username'>.

ee/packages/abac/src/user-auto-removal.spec.ts (2)

370-404: Nice large-population sanity test.

Good balance between coverage and runtime; assertions are clear and deterministic.


20-26: Overall: solid integration harness and coverage.

Setup/teardown are clean; scenarios match PR intent (new attributes tighten access). Assertions on memberships and idempotency look good.

Please add one assertion somewhere (after a removal) verifying Room.removeUserFromRoom was called with the ABAC-specific options once you switch the mock to jest.fn (see earlier comment).

Also applies to: 45-75, 83-92, 99-103

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
ee/packages/abac/src/index.ts (4)

1-10: Good concurrency control setup.

The addition of p-limit to control concurrent user removals is a solid reliability pattern. A limit of 20 concurrent operations should prevent server overload while maintaining reasonable throughput.

Consider making the concurrency limit configurable via environment variable or system setting if you need to tune it based on production load characteristics or server capacity.


197-218: Consider renaming prevMap to currentMap for consistency.

The logic correctly implements the "additions only" policy described in the PR objectives. However, the variable prevMap is built from the current parameter, creating a slight naming inconsistency.

Apply this diff to improve clarity:

 private didAttributesChange(current: IAbacAttributeDefinition[], next: IAbacAttributeDefinition[]) {
   let added = false;
-  const prevMap = new Map(current.map((a) => [a.key, new Set(a.values)]));
+  const currentMap = new Map(current.map((a) => [a.key, new Set(a.values)]));
   for (const { key, values } of next) {
-    const prevValues = prevMap.get(key);
-    if (!prevValues) {
+    const currentValues = currentMap.get(key);
+    if (!currentValues) {
       added = true;
       break;
     }
     for (const v of values) {
-      if (!prevValues.has(v)) {
+      if (!currentValues.has(v)) {
         added = true;
         break;
       }

468-468: Use Promise.allSettled instead of Promise.all for resilience.

Promise.all fails fast on the first rejection, meaning if one user removal fails, the remaining removals won't complete. This could leave the room in an inconsistent state with some non-compliant users not removed.

Apply this diff:

-      await Promise.all(userRemovalPromises);
+      const results = await Promise.allSettled(userRemovalPromises);
+      const failures = results.filter((r) => r.status === 'rejected');
+      if (failures.length > 0) {
+        this.logger.warn({
+          msg: 'Some user removals failed',
+          rid,
+          failureCount: failures.length,
+          totalAttempted: results.length,
+        });
+      }

428-430: Redundant check can be removed.

The check if (!nonCompliantConditions.length) is redundant because:

  • nonCompliantConditions is created by mapping over newAttributes
  • Line 406 already guards against empty/falsy newAttributes with an early return
  • If we reach line 428, newAttributes.length > 0, so nonCompliantConditions.length > 0

You can safely remove lines 428-430.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a690de7 and 9894571.

📒 Files selected for processing (1)
  • ee/packages/abac/src/index.ts (10 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ee/packages/abac/src/index.ts (3)
packages/core-services/src/index.ts (2)
  • IAbacService (56-56)
  • Room (171-171)
packages/core-services/src/types/IAbacService.ts (1)
  • IAbacService (3-20)
packages/core-typings/src/IAbacAttribute.ts (1)
  • IAbacAttributeDefinition (3-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (4)
ee/packages/abac/src/index.ts (4)

15-20: LGTM!

Proper logger initialization following standard patterns. The structured logging throughout the service improves observability.


113-116: Excellent logging additions.

Replacing inline operations with structured logger calls significantly improves observability. The contextual information (room IDs, attributes, operations) will be valuable for debugging and auditing ABAC operations.

Also applies to: 137-140, 186-192, 288-292, 310-316, 334-338, 361-365, 382-386, 397-401


437-450: Performance testing for large rooms required. The code streams users with an async iterator and throttles removals via p-limit, but please load-test scenarios with thousands of users to confirm it completes within acceptable time.


417-436: Query correctly implements ABAC non-compliance. The $or of $not: { $elemMatch: { key, values: { $all: values } } } conditions will remove any user missing at least one required attribute, matching the intended “must satisfy all” semantics.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (9)
ee/packages/abac/src/index.ts (9)

10-10: Rename concurrency limiter to avoid shadowing and confusion with pagination limit

limit from p-limit shadows local limit used in listAbacAttributes, which hurts readability and risks mistakes.

-const limit = pLimit(20);
+const withConcurrency = pLimit(20);
-					limit(() =>
+					withConcurrency(() =>

Also applies to: 442-442


460-465: Reduce PII/noise in logs; log counts instead of full user ID arrays

Logging full usersToRemove leaks PII and can explode log volume for large rooms. Prefer counts.

-			this.logger.debug({
-				msg: 'Re-evaluating room subscriptions',
-				rid,
-				newAttributes,
-				usersThatWillBeRemoved: usersToRemove,
-			});
+			this.logger.debug({
+				msg: 'Re-evaluating room subscriptions',
+				rid,
+				newAttributes,
+				usersRemovalCount: usersToRemove.length,
+			});

467-467: Make removals resilient; avoid failing the whole batch on a single error

Promise.all fails fast. Use Promise.allSettled so one failed removal doesn’t abort the rest.

-			await Promise.all(userRemovalPromises);
+			await Promise.allSettled(userRemovalPromises);

438-438: Type the promises array for clarity

Avoid any[] and keep type-safety.

-			const userRemovalPromises = [];
+			const userRemovalPromises: Array<Promise<unknown>> = [];

197-197: Add explicit return type

Be explicit for maintainability.

-	private didAttributesChange(current: IAbacAttributeDefinition[], next: IAbacAttributeDefinition[]) {
+	private didAttributesChange(current: IAbacAttributeDefinition[], next: IAbacAttributeDefinition[]): boolean {

184-195: Optional: sanitize attribute values in logs

Debug logs include full previous/new attribute values. If values may be sensitive, prefer logging keys/counts only.


9-10: Concurrency cap: consider configurability

A fixed cap of 20 may be suboptimal across deployments. Consider making it configurable (env/setting) or deriving from CPU count.


427-430: Redundant length check

nonCompliantConditions is derived directly from newAttributes (already checked for length). The extra length guard is redundant.


436-449: Narrow Users.find projection
The call currently fetches full user documents (excluding __rooms) but removeUserFromRoom only reads user._id and user.username. Change to:

const cursor = Users.find(query, { projection: { _id: 1, username: 1 } });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a690de7 and 273d7ae.

📒 Files selected for processing (1)
  • ee/packages/abac/src/index.ts (10 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ee/packages/abac/src/index.ts (3)
packages/core-services/src/index.ts (2)
  • IAbacService (56-56)
  • Room (171-171)
packages/core-services/src/types/IAbacService.ts (1)
  • IAbacService (3-20)
packages/core-typings/src/IAbacAttribute.ts (1)
  • IAbacAttributeDefinition (3-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
ee/packages/abac/src/index.ts (1)

381-393: Resolved: gated re-evaluation on replace to only when stricter (new values added)

This addresses the earlier review concern. Looks good.

🧹 Nitpick comments (4)
ee/packages/abac/src/index.ts (4)

411-482: Stream removals and use allSettled to bound memory and improve resilience

Pushing every removal promise into an array can OOM large rooms; a single failure also rejects the whole batch. Stream/batch with a cap and use Promise.allSettled.

-			const cursor = Users.find(query, { projection: { __rooms: 0 } });
-			const usersToRemove: string[] = [];
-			const userRemovalPromises = [];
-			for await (const doc of cursor) {
-				usersToRemove.push(doc._id);
-				userRemovalPromises.push(
-					limit(() =>
-						Room.removeUserFromRoom(rid, doc, {
-							skipAppPreEvents: true,
-							customSystemMessage: 'abac-removed-user-from-room' as const,
-						}),
-					),
-				);
-			}
+			const cursor = Users.find(query, { projection: { __rooms: 0 } });
+			const usersToRemove: string[] = [];
+			const pending: Promise<unknown>[] = [];
+			const BATCH = 200;
+			for await (const doc of cursor) {
+				usersToRemove.push(doc._id);
+				pending.push(
+					removeOpsLimit(() =>
+						Room.removeUserFromRoom(rid, doc, {
+							skipAppPreEvents: true,
+							customSystemMessage: 'abac-removed-user-from-room' as const,
+						}),
+					),
+				);
+				if (pending.length >= BATCH) {
+					await Promise.allSettled(pending);
+					pending.length = 0;
+				}
+			}
@@
-			this.logger.debug({
-				msg: 'Re-evaluating room subscriptions',
-				rid,
-				newAttributes,
-				usersThatWillBeRemoved: usersToRemove,
-			});
+			this.logger.debug({
+				msg: 'Re-evaluating room subscriptions',
+				rid,
+				newAttributes,
+				usersToRemoveCount: usersToRemove.length,
+			});
@@
-			await Promise.all(userRemovalPromises);
+			if (pending.length) {
+				await Promise.allSettled(pending);
+			}

Notes:

  • Consider projecting only fields required by Room.removeUserFromRoom (e.g., _id, username) to reduce payloads if API allows.

9-10: Rename p-limit handle to avoid confusion with local “limit” variables

There’s a method-scoped const limit used for pagination (Line 52), which shadows the module-level p-limit const. Rename the p-limit handle for clarity.

-const limit = pLimit(20);
+const removeOpsLimit = pLimit(20);
@@
-					limit(() =>
+					removeOpsLimit(() =>

Also applies to: 449-455


197-218: didAttributesChange only detects stricter changes (new keys/values) — clarify intent

Logic is correct for “only re-eval when stricter.” Consider renaming to isMoreRestrictive or add a short comment for future readers.


298-305: Avoid unnecessary writes on value re-order; normalize before compare

Order-only changes hit the DB (intended) but can be noisy. Normalize arrays (e.g., sort unique) before comparing and persisting.

-		if (prevValues.length === values.length && prevValues.every((v, i) => v === values[i])) {
+		const normPrev = [...prevValues].sort();
+		const normNext = [...values].sort();
+		if (normPrev.length === normNext.length && normPrev.every((v, i) => v === normNext[i])) {
 			return;
 		}
@@
-		const prevSet = new Set(prevValues);
-		const added = values.some((v) => !prevSet.has(v));
+		const prevSet = new Set(normPrev);
+		const added = normNext.some((v) => !prevSet.has(v));
@@
-		await Rooms.updateAbacAttributeValuesArrayFilteredById(rid, key, values);
+		await Rooms.updateAbacAttributeValuesArrayFilteredById(rid, key, normNext);
@@
-			const next = previous.map((a, i) => (i === existingIndex ? { key, values } : a));
+			const next = previous.map((a, i) => (i === existingIndex ? { key, values: normNext } : a));

Also applies to: 307-317

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 273d7ae and d8b0d8a.

📒 Files selected for processing (1)
  • ee/packages/abac/src/index.ts (10 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ee/packages/abac/src/index.ts (3)
packages/core-services/src/index.ts (2)
  • IAbacService (56-56)
  • Room (171-171)
packages/core-services/src/types/IAbacService.ts (1)
  • IAbacService (3-20)
packages/core-typings/src/IAbacAttribute.ts (1)
  • IAbacAttributeDefinition (3-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (1)
ee/packages/abac/src/index.ts (1)

184-195: LGTM: setRoomAbacAttributes re-eval gated by didAttributesChange with clear logging

Matches the PR’s intent and avoids unnecessary scans.

@KevLehman KevLehman requested a review from a team as a code owner October 15, 2025 15:09
@KevLehman KevLehman force-pushed the feat/on-attribute-changed-hooks branch from d8b0d8a to a99bcbc Compare October 15, 2025 16:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
apps/meteor/app/lib/server/functions/removeUserFromRoom.ts (1)

13-17: Address the byUser type mismatch.

As noted in previous reviews, byUser?: IUser should be byUser?: Pick<IUser, '_id' | 'username'> to match the IRoomService interface and simplify caller requirements.

apps/meteor/server/services/room/service.ts (1)

87-93: Address the byUser type mismatch.

As flagged in previous reviews, byUser?: IUser should be byUser?: Pick<IUser, '_id' | 'username'> to match the IRoomService interface declaration.

🧹 Nitpick comments (1)
apps/meteor/server/lib/dataExport/exportRoomMessagesToFile.ts (1)

176-176: Consider adding 'abac-removed-user-from-room' to italicTypes.

The new ABAC removal message is a system-automated action, similar to other message types in the italicTypes array (e.g., 'ru', 'au', 'ul', 'uj'). For consistent HTML export formatting, consider including it.

Apply this diff:

-const italicTypes: IMessage['t'][] = ['uj', 'ul', 'au', 'r', 'ru', 'wm', 'livechat-close'];
+const italicTypes: IMessage['t'][] = ['uj', 'ul', 'au', 'r', 'ru', 'wm', 'livechat-close', 'abac-removed-user-from-room'];
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d8b0d8a and a99bcbc.

📒 Files selected for processing (12)
  • apps/meteor/app/lib/server/functions/removeUserFromRoom.ts (3 hunks)
  • apps/meteor/server/lib/dataExport/exportRoomMessagesToFile.ts (1 hunks)
  • apps/meteor/server/services/room/service.ts (2 hunks)
  • ee/packages/abac/package.json (1 hunks)
  • ee/packages/abac/src/index.spec.ts (6 hunks)
  • ee/packages/abac/src/index.ts (10 hunks)
  • ee/packages/abac/src/user-auto-removal.spec.ts (1 hunks)
  • packages/apps-engine/src/definition/messages/MessageType.ts (1 hunks)
  • packages/core-services/src/types/IRoomService.ts (2 hunks)
  • packages/core-typings/src/IMessage/IMessage.ts (1 hunks)
  • packages/http-router/src/Router.ts (1 hunks)
  • packages/i18n/src/locales/en.i18n.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core-typings/src/IMessage/IMessage.ts
  • packages/apps-engine/src/definition/messages/MessageType.ts
  • ee/packages/abac/package.json
🧰 Additional context used
🧬 Code graph analysis (6)
packages/core-services/src/types/IRoomService.ts (1)
packages/core-typings/src/IMessage/IMessage.ts (1)
  • MessageTypesValues (114-114)
apps/meteor/server/services/room/service.ts (1)
packages/core-typings/src/IMessage/IMessage.ts (1)
  • MessageTypesValues (114-114)
ee/packages/abac/src/user-auto-removal.spec.ts (5)
ee/packages/abac/src/index.ts (1)
  • AbacService (13-487)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (22-98)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-255)
packages/core-typings/src/IAbacAttribute.ts (1)
  • IAbacAttributeDefinition (3-14)
packages/models/src/index.ts (1)
  • registerServiceModels (231-265)
apps/meteor/app/lib/server/functions/removeUserFromRoom.ts (2)
apps/meteor/server/services/room/service.ts (1)
  • removeUserFromRoom (87-93)
packages/core-typings/src/IMessage/IMessage.ts (1)
  • MessageTypesValues (114-114)
ee/packages/abac/src/index.ts (3)
packages/core-services/src/index.ts (2)
  • IAbacService (56-56)
  • Room (171-171)
packages/core-services/src/types/IAbacService.ts (1)
  • IAbacService (3-20)
packages/core-typings/src/IAbacAttribute.ts (1)
  • IAbacAttributeDefinition (3-14)
packages/http-router/src/Router.ts (1)
apps/meteor/tests/e2e/page-objects/fragments/message.ts (1)
  • body (6-8)
🪛 Biome (2.1.2)
ee/packages/abac/src/user-auto-removal.spec.ts

[error] 10-10: Expected an identifier, an array pattern, or an object pattern but instead found 'class'.

Expected an identifier, an array pattern, or an object pattern here.

(parse)


[error] 10-10: expected : but instead found {

Remove {

(parse)


[error] 13-13: ';' expected'

An explicit or implicit semicolon is expected here...

(parse)


[error] 13-13: ';' expected'

An explicit or implicit semicolon is expected here...

(parse)


[error] 14-14: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 15-15: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 17-17: Expected an expression but instead found '}'.

Expected an expression here.

(parse)


[error] 10-10: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL-Build
🔇 Additional comments (10)
packages/http-router/src/Router.ts (1)

267-267: LGTM! Improved test-mode debugging.

Adding the actual response body to the validation failure log provides valuable debugging context when investigating schema mismatches during testing.

apps/meteor/server/lib/dataExport/exportRoomMessagesToFile.ts (1)

158-160: LGTM! New ABAC message type correctly handled.

The new case follows the established pattern for system message types and uses the appropriate i18n key.

packages/core-services/src/types/IRoomService.ts (1)

1-1: LGTM! Clean API signature update.

The type narrowing for byUser to Pick<IUser, '_id' | 'username'> is appropriate for a public API boundary, and the new optional fields (skipAppPreEvents, customSystemMessage) are well-typed and support the ABAC re-evaluation feature.

Also applies to: 44-48

apps/meteor/app/lib/server/functions/removeUserFromRoom.ts (1)

24-35: LGTM! Pre-event gating and custom message handling are correct.

The conditional logic correctly:

  • Skips app pre-events when skipAppPreEvents is true (needed for ABAC auto-removal)
  • Preserves post-event triggering at line 89
  • Applies custom system messages when provided

Also applies to: 48-50

ee/packages/abac/src/index.spec.ts (1)

419-429: Excellent test coverage for hook triggering logic.

The updated test expectations correctly verify that onRoomAttributesChanged:

  • Fires when new attributes or values are added (more restrictive)
  • Does NOT fire when attributes or values are removed (less restrictive)

This aligns perfectly with the PR objectives to re-evaluate only when room constraints become stricter.

Also applies to: 431-455, 532-545, 574-584

ee/packages/abac/src/index.ts (3)

1-11: LGTM! Proper concurrency control and logging setup.

The addition of p-limit with a 20-concurrent limit and Logger initialization provides good control over resource usage during bulk user removals.

Also applies to: 16-21


201-222: Clean helper for detecting restrictive changes.

The didAttributesChange method correctly identifies when attributes become more restrictive by detecting:

  • New attribute keys
  • New values added to existing keys

This properly gates re-evaluation to only when needed.


415-486: Solid re-evaluation implementation with good error handling.

The onRoomAttributesChanged implementation:

  • ✅ Correctly builds non-compliance conditions
  • ✅ Uses controlled concurrency with p-limit
  • ✅ Skips app pre-events for ABAC removals (line 455)
  • ✅ Uses custom system message 'abac-removed-user-from-room' (line 456)
  • ✅ Proper error handling and logging

Note: Index optimization for the $or query at lines 442-445 is acknowledged in past reviews as deferred to when production data warrants it.

ee/packages/abac/src/user-auto-removal.spec.ts (2)

9-18: Ignore static analysis false positives.

The Biome parser errors are false positives related to the jest.mock factory function syntax, which is valid Jest code. The mock correctly simulates the subscription removal side effect.


104-404: Excellent integration test coverage.

The test suite comprehensively validates:

  • ✅ Correct user removal when attributes become more restrictive
  • ✅ No removal when attributes become less restrictive
  • ✅ AND semantics across multiple attributes
  • ✅ Idempotency (no duplicate removals)
  • ✅ Edge cases (supersets, missing keys, no ABAC attributes)
  • ✅ Performance sanity check with 300 users

Each test verifies both the logging payload and actual membership changes, ensuring end-to-end correctness.

@KevLehman KevLehman force-pushed the feat/on-attribute-changed-hooks branch from e3409c8 to 1d0e210 Compare October 15, 2025 18:46
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/models/src/models/Rooms.ts (1)

966-972: Good genericization; avoid the as any cast for stronger type-safety

The API change is sound and backward compatible. However, the as any on the filter hides mismatches between the collection schema (IRoom) and arbitrary T.

Consider either:

  • Constrain T to extend IRoom and keep FindOptions, casting the return only, or
  • Provide overloads (default returning IRoom, projected variant returning T) so the filter stays Filter.

Example adjustment to drop as any:

-findOneByIdAndType<T extends Document = IRoom>(
+findOneByIdAndType<T extends IRoom = IRoom>(
   roomId: IRoom['_id'],
   type: IRoom['t'],
-  options: FindOptions<T> = {} as FindOptions<T>,
+  options: FindOptions<IRoom> = {},
 ): Promise<T | null> {
-  return this.findOne<T>({ _id: roomId, t: type } as any, options);
+  return this.findOne({ _id: roomId, t: type }, options) as unknown as T;
 }

If you need T for projections narrower than IRoom, prefer overloads to keep filter typing honest. Also, consider leaving options as optional (no default) to avoid casting an empty object.

packages/model-typings/src/models/IRoomsModel.ts (1)

208-208: Interface update looks good; consider tighter generic bound

The generic signature aligns with the implementation and preserves the default return type. To reduce accidental misuse, consider T extends IRoom = IRoom instead of Document, or add documentation indicating T should represent a projection of IRoom.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a99bcbc and e3409c8.

📒 Files selected for processing (5)
  • ee/packages/abac/src/index.spec.ts (10 hunks)
  • ee/packages/abac/src/index.ts (12 hunks)
  • ee/packages/abac/src/user-auto-removal.spec.ts (1 hunks)
  • packages/model-typings/src/models/IRoomsModel.ts (1 hunks)
  • packages/models/src/models/Rooms.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
ee/packages/abac/src/index.ts (4)
packages/core-services/src/index.ts (2)
  • IAbacService (56-56)
  • Room (171-171)
packages/core-services/src/types/IAbacService.ts (1)
  • IAbacService (3-20)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (22-98)
packages/core-typings/src/IAbacAttribute.ts (1)
  • IAbacAttributeDefinition (3-14)
packages/model-typings/src/models/IRoomsModel.ts (1)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (22-98)
ee/packages/abac/src/user-auto-removal.spec.ts (5)
ee/packages/abac/src/index.ts (1)
  • AbacService (13-464)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (22-98)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-255)
packages/core-typings/src/IAbacAttribute.ts (1)
  • IAbacAttributeDefinition (3-14)
packages/models/src/index.ts (1)
  • registerServiceModels (231-265)
🪛 Biome (2.1.2)
ee/packages/abac/src/user-auto-removal.spec.ts

[error] 10-10: Expected an identifier, an array pattern, or an object pattern but instead found 'class'.

Expected an identifier, an array pattern, or an object pattern here.

(parse)


[error] 10-10: expected : but instead found {

Remove {

(parse)


[error] 13-13: ';' expected'

An explicit or implicit semicolon is expected here...

(parse)


[error] 13-13: ';' expected'

An explicit or implicit semicolon is expected here...

(parse)


[error] 14-14: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 15-15: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 17-17: Expected an expression but instead found '}'.

Expected an expression here.

(parse)


[error] 10-10: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL-Build
🔇 Additional comments (16)
ee/packages/abac/src/index.spec.ts (6)

419-431: LGTM! Test correctly verifies hook invocation on new key addition.

The test correctly expects onRoomAttributesChanged to be called when a new key is added, including handling of duplicate values. The assertion that the hook receives the room object (not just the ID) aligns with the updated signature.


433-444: LGTM! Test correctly verifies no hook invocation on value removal.

This test correctly implements the PR intent that removing values (making requirements less strict) does not trigger re-evaluation.


446-459: LGTM! Test correctly verifies hook invocation on value addition.

This test correctly implements the PR intent that adding values (making requirements more strict) triggers re-evaluation.


536-551: LGTM! Tests correctly verify conditional hook invocation.

Both tests correctly implement the PR intent:

  • Line 536-543: Hook fires when values are added (more restrictive)
  • Line 545-551: Hook does not fire when values are removed (less restrictive)

580-590: LGTM! Test correctly verifies no hook invocation on attribute removal.

This test correctly implements the PR intent that removing an entire attribute does not trigger re-evaluation.


624-740: LGTM! Tests correctly verify hook invocations with updated attributes.

All tests correctly verify that:

  • The hook is called when attributes are added or replaced
  • The hook receives the updated attributes from the DB (or constructed list as fallback)
  • The tests cover both cases: DB returning updated doc and DB returning undefined
ee/packages/abac/src/index.ts (8)

1-11: LGTM! Concurrency control setup is appropriate.

The use of pLimit(20) to control concurrent user removals is a good safeguard against overloading the server. This limit balances throughput with resource consumption.


16-21: LGTM! Logger initialization is appropriate.

The logger initialization in the constructor provides good observability for ABAC operations.


196-217: LGTM! Attribute change detection is correct and efficient.

The didAttributesChange helper correctly detects when attributes become more restrictive by checking for:

  1. New keys (not in previous attributes)
  2. New values within existing keys (not in previous values set)

The use of Map and Set provides O(1) lookups, and short-circuiting on first addition keeps it efficient.


306-309: LGTM! Value addition detection is correct.

The wereAttributeValuesAdded helper correctly detects when values are added to an attribute using Set-based lookup.


388-399: LGTM! Non-compliant user query logic is correct.

The buildNonCompliantConditions helper correctly constructs MongoDB queries to find users who don't satisfy the room's ABAC requirements. The use of $not with $elemMatch and $all correctly identifies users missing either:

  • The attribute key entirely
  • One or more required values for an existing key

MongoDB's handling of missing fields ensures users without abacAttributes are also correctly identified as non-compliant.


401-463: LGTM! User re-evaluation and removal logic is well-implemented.

The onRoomAttributesChanged implementation correctly:

  1. Handles the edge case of empty attributes (room becomes non-ABAC)
  2. Builds accurate non-compliant user queries using __rooms filter to target only current members
  3. Controls concurrency with pLimit to prevent server overload
  4. Uses new skipAppPreEvents and customSystemMessage options for appropriate removal notifications
  5. Provides comprehensive logging for observability
  6. Handles errors gracefully without disrupting other operations

The logic correctly implements the PR objective of removing users who no longer satisfy stricter ABAC requirements.


177-179: LGTM! Room fetch projections are correctly expanded.

All room fetch operations now correctly include t and teamMain fields in the projection to satisfy the updated onRoomAttributesChanged signature.

Also applies to: 268-270, 334-336, 359-361


117-120: LGTM! Logging additions improve observability.

The debug logging for attribute updates, deletions, and room attribute removals provides good operational visibility into ABAC operations.

Also applies to: 141-144, 324-328

ee/packages/abac/src/user-auto-removal.spec.ts (2)

1-102: LGTM! Integration test infrastructure is well-designed.

The test setup demonstrates several best practices:

  • Uses MongoMemoryServer for isolated, reproducible integration tests
  • Mocks Room.removeUserFromRoom appropriately to focus on subscription changes
  • Provides clear helper functions (insertDefinitions, insertRoom, insertUsers) for readable test cases
  • Spies on logger to verify observability behavior
  • Properly cleans up resources in beforeEach and afterAll

104-410: LGTM! Comprehensive integration test coverage.

The integration tests provide excellent coverage of the ABAC user removal functionality:

Scenarios covered:

  1. New key addition (104-167): Verifies compliant users stay, non-compliant users are removed
  2. Duplicate values (145-166): Ensures duplicate values are handled correctly
  3. Value addition/removal (169-219): Confirms re-evaluation only on additions
  4. Multi-attribute enforcement (221-288): Tests AND semantics across multiple attributes
  5. Idempotency (290-322): Confirms no duplicate removals on repeated operations
  6. Edge cases (324-374): Tests supersets, missing keys, and missing attributes
  7. Large populations (376-410): Lightweight performance sanity check with 300 users

Verification approach:

  • Asserts both logging output and actual database state changes
  • Verifies hook invocation with correct parameters
  • Confirms membership updates for both retained and removed users
  • Uses realistic ABAC attribute combinations

This comprehensive test suite gives high confidence in the correctness of the implementation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
packages/i18n/src/locales/en.i18n.json (1)

17-17: Restore the username placeholder for this system message.

The value still renders only “was removed by ABAC,” so rooms never see which member was dropped. Please keep the {{username}} placeholder (e.g., @{{username}} was removed by ABAC) to match the key contract.

-  "abac_removed_user_from_the_room": "was removed by ABAC",
+  "abac_removed_user_from_the_room": "@{{username}} was removed by ABAC",
apps/meteor/app/lib/server/functions/removeUserFromRoom.ts (1)

24-35: Minor: catch type can be unknown (already raised before)

You can change catch (error: any) to catch (error: unknown) and narrow, for stricter typing. This was mentioned in earlier reviews; consider it a nit.

🧹 Nitpick comments (3)
packages/models/src/models/Rooms.ts (1)

966-972: Consider the safety implications of the as any cast.

The generic type parameter improves flexibility, but the as any cast on line 971 bypasses type checking for the filter. While this is likely necessary for the generic implementation, it allows callers to specify a type T that may not accurately represent the actual document structure returned by the query.

Consider whether the projection in FindOptions<T> provides sufficient safety, or if additional runtime validation would be beneficial to ensure type consistency.

ee/packages/abac/src/index.ts (1)

10-11: Consider making the concurrency limit configurable.

The hardcoded limit of 20 may need adjustment based on server capacity and load patterns. Consider making this configurable through a setting or environment variable to allow tuning without code changes.

ee/packages/abac/src/index.spec.ts (1)

197-218: Prefer asserting the hook wasn’t called over log-string filtering

Use the existing (service as any).onRoomAttributesChanged spy to assert non-invocation for removal-only, instead of relying on a specific debug message string.

Apply:

-const evaluationCalls = debugSpy.mock.calls
-  .map((c: any[]) => c[0])
-  .filter((arg: any) => arg && arg.msg === 'Re-evaluating room subscriptions');
-expect(evaluationCalls.length).toBe(0);
+expect((service as any).onRoomAttributesChanged).not.toHaveBeenCalled();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e3409c8 and 1d0e210.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (15)
  • apps/meteor/app/lib/server/functions/removeUserFromRoom.ts (3 hunks)
  • apps/meteor/server/lib/dataExport/exportRoomMessagesToFile.ts (1 hunks)
  • apps/meteor/server/services/room/service.ts (2 hunks)
  • ee/packages/abac/package.json (1 hunks)
  • ee/packages/abac/src/index.spec.ts (10 hunks)
  • ee/packages/abac/src/index.ts (11 hunks)
  • ee/packages/abac/src/user-auto-removal.spec.ts (1 hunks)
  • packages/apps-engine/src/definition/messages/MessageType.ts (1 hunks)
  • packages/core-services/src/types/IRoomService.ts (2 hunks)
  • packages/core-typings/src/IMessage/IMessage.ts (1 hunks)
  • packages/http-router/src/Router.ts (1 hunks)
  • packages/i18n/src/locales/en.i18n.json (1 hunks)
  • packages/message-types/src/registrations/common.ts (1 hunks)
  • packages/model-typings/src/models/IRoomsModel.ts (1 hunks)
  • packages/models/src/models/Rooms.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • ee/packages/abac/package.json
  • packages/apps-engine/src/definition/messages/MessageType.ts
  • packages/http-router/src/Router.ts
  • packages/model-typings/src/models/IRoomsModel.ts
  • packages/core-typings/src/IMessage/IMessage.ts
  • apps/meteor/server/services/room/service.ts
  • packages/message-types/src/registrations/common.ts
  • apps/meteor/server/lib/dataExport/exportRoomMessagesToFile.ts
🧰 Additional context used
🧬 Code graph analysis (5)
packages/core-services/src/types/IRoomService.ts (1)
packages/core-typings/src/IMessage/IMessage.ts (1)
  • MessageTypesValues (114-114)
packages/models/src/models/Rooms.ts (1)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (22-98)
apps/meteor/app/lib/server/functions/removeUserFromRoom.ts (2)
apps/meteor/server/services/room/service.ts (1)
  • removeUserFromRoom (87-93)
packages/core-typings/src/IMessage/IMessage.ts (1)
  • MessageTypesValues (114-114)
ee/packages/abac/src/index.ts (4)
packages/core-services/src/index.ts (2)
  • IAbacService (56-56)
  • Room (171-171)
packages/core-services/src/types/IAbacService.ts (1)
  • IAbacService (3-20)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (22-98)
packages/core-typings/src/IAbacAttribute.ts (1)
  • IAbacAttributeDefinition (3-14)
ee/packages/abac/src/user-auto-removal.spec.ts (5)
ee/packages/abac/src/index.ts (1)
  • AbacService (13-459)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (22-98)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-255)
packages/core-typings/src/IAbacAttribute.ts (1)
  • IAbacAttributeDefinition (3-14)
packages/models/src/index.ts (1)
  • registerServiceModels (231-265)
🪛 Biome (2.1.2)
ee/packages/abac/src/user-auto-removal.spec.ts

[error] 10-10: Expected an identifier, an array pattern, or an object pattern but instead found 'class'.

Expected an identifier, an array pattern, or an object pattern here.

(parse)


[error] 10-10: expected : but instead found {

Remove {

(parse)


[error] 13-13: ';' expected'

An explicit or implicit semicolon is expected here...

(parse)


[error] 13-13: ';' expected'

An explicit or implicit semicolon is expected here...

(parse)


[error] 14-14: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 15-15: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 17-17: Expected an expression but instead found '}'.

Expected an expression here.

(parse)


[error] 10-10: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (11)
ee/packages/abac/src/index.ts (9)

18-21: LGTM!

Logger initialization is clean and will provide valuable debugging and monitoring capabilities.


173-190: LGTM!

The conditional check using didAttributesChange correctly ensures that onRoomAttributesChanged is only called when attributes become more restrictive, aligning with the PR's objectives.


192-213: LGTM!

The didAttributesChange helper correctly identifies when new attribute values are added, triggering re-evaluation only when room requirements become more restrictive. The Set-based comparison is efficient.


262-284: LGTM!

The method correctly triggers re-evaluation when a new attribute key is added to the room, passing the necessary room context to onRoomAttributesChanged.


301-304: LGTM!

The helper efficiently detects when new attribute values are added, ensuring re-evaluation occurs only when necessary.


351-381: LGTM!

The method correctly handles both scenarios:

  • When replacing an existing attribute, it only triggers re-evaluation if new values were added
  • When adding a new attribute, it always triggers re-evaluation

This aligns with the PR's objective to re-evaluate only when requirements become stricter.


383-394: LGTM!

The query construction correctly identifies users who don't satisfy all required attribute values using $not, $elemMatch, and $all operators.


396-458: Implementation successfully addresses PR objectives.

The onRoomAttributesChanged method effectively implements the core functionality:

  • Identifies users who no longer meet room requirements
  • Removes them with proper concurrency control
  • Logs operations for debugging
  • Handles edge cases (empty attributes, no users to remove)

This implementation aligns with the PR's goal to re-evaluate and remove users when room ABAC attributes become more restrictive.


451-457: Consider surfacing failures from onRoomAttributesChanged

In ee/packages/abac/src/index.ts (lines 451–457), the catch block logs errors but swallows them, so callers and monitoring won’t detect if re-evaluation or user removals fail. Evaluate whether to rethrow (after best-effort removals) or emit an event/metric for visibility.

packages/core-services/src/types/IRoomService.ts (1)

44-48: Public API looks good; ensure all implementers align with byUser Pick type

Interface is correct. Please align apps/meteor/server/services/room/service.ts and apps/meteor/app/lib/server/functions/removeUserFromRoom.ts to use byUser?: Pick<IUser, '_id' | 'username'> to avoid TS mismatches.

ee/packages/abac/src/index.spec.ts (1)

420-432: Tests correctly reflect “additions trigger, removals don’t” semantics

Assertions using post-operation attributes and guarding no-calls on removals look good and match the new contract.

Also applies to: 434-460, 537-544, 546-552, 581-591, 635-639, 651-655, 722-726, 737-741

@KevLehman KevLehman force-pushed the feat/on-attribute-changed-hooks branch from 1d0e210 to 5c3f9b9 Compare October 15, 2025 20:21
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
apps/meteor/app/lib/server/functions/removeUserFromRoom.ts (1)

13-17: Critical: byUser type mismatch with IRoomService interface.

The implementation uses byUser?: IUser but IRoomService declares byUser?: Pick<IUser, '_id' | 'username'>. This breaks type safety and can cause runtime errors when callers provide a minimal object.

Apply this diff to align with the interface:

 export const removeUserFromRoom = async function (
 	rid: string,
 	user: IUser,
-	options?: { byUser?: IUser; skipAppPreEvents?: boolean; customSystemMessage?: MessageTypesValues },
+	options?: { byUser?: Pick<IUser, '_id' | 'username'>; skipAppPreEvents?: boolean; customSystemMessage?: MessageTypesValues },
 ): Promise<void> {

Then cast byUser to IUser at the Apps event call sites (lines 27, 89):

-			await Apps.self?.triggerEvent(AppEvents.IPreRoomUserLeave, room, user, options?.byUser);
+			await Apps.self?.triggerEvent(AppEvents.IPreRoomUserLeave, room, user, options?.byUser as IUser | undefined);
-	await Apps.self?.triggerEvent(AppEvents.IPostRoomUserLeave, room, user, options?.byUser);
+	await Apps.self?.triggerEvent(AppEvents.IPostRoomUserLeave, room, user, options?.byUser as IUser | undefined);
apps/meteor/server/services/room/service.ts (1)

87-92: Critical: byUser type mismatch with IRoomService interface.

Same as the implementation file, this method signature uses byUser?: IUser instead of byUser?: Pick<IUser, '_id' | 'username'> as declared in the interface.

Apply this diff:

 	async removeUserFromRoom(
 		roomId: string,
 		user: IUser,
-		options?: { byUser?: IUser; skipAppPreEvents?: boolean; customSystemMessage?: MessageTypesValues },
+		options?: { byUser?: Pick<IUser, '_id' | 'username'>; skipAppPreEvents?: boolean; customSystemMessage?: MessageTypesValues },
 	): Promise<void> {
 		return removeUserFromRoom(roomId, user, options);
 	}
ee/packages/abac/src/user-auto-removal.spec.ts (1)

9-18: Fix jest.mock factory syntax to resolve parse errors.

The inline class {} syntax in the factory causes Biome parse errors. Additionally, the removeUserFromRoom mock should accept the optional options parameter to match the real signature.

Apply this diff:

-jest.mock('@rocket.chat/core-services', () => ({
-	ServiceClass: class {},
-	Room: {
-		// Mimic the DB side-effects of removing a user from a room (no apps/system messages)
-		removeUserFromRoom: async (roomId: string, user: any) => {
-			const { Subscriptions } = await import('@rocket.chat/models');
-			await Subscriptions.removeByRoomIdAndUserId(roomId, user._id);
-		},
-	},
-}));
+jest.mock('@rocket.chat/core-services', () => {
+	class MockServiceClass {}
+	const Room = {
+		// Mimic the DB side-effects of removing a user from a room (no apps/system messages)
+		removeUserFromRoom: async (roomId: string, user: any, _options?: any) => {
+			const { Subscriptions } = await import('@rocket.chat/models');
+			await Subscriptions.removeByRoomIdAndUserId(roomId, user._id);
+		},
+	};
+	return { ServiceClass: MockServiceClass, Room };
+});
🧹 Nitpick comments (2)
ee/packages/abac/src/index.ts (2)

192-213: Consider extracting the inner loop for clarity.

The logic is correct, but the nested breaks make the flow less obvious.

Apply this diff for a cleaner structure:

 private didAttributesChange(current: IAbacAttributeDefinition[], next: IAbacAttributeDefinition[]) {
-  let added = false;
   const prevMap = new Map(current.map((a) => [a.key, new Set(a.values)]));
   for (const { key, values } of next) {
     const prevValues = prevMap.get(key);
     if (!prevValues) {
-      added = true;
-      break;
+      return true;
     }
     for (const v of values) {
       if (!prevValues.has(v)) {
-        added = true;
-        break;
+        return true;
       }
     }
-    if (added) {
-      break;
-    }
   }
-
-  return added;
+  return false;
 }

354-381: LGTM! Handles both new and existing attribute replacements correctly.

The method properly:

  • Checks if values were added before re-evaluating existing keys (line 367)
  • Always re-evaluates when inserting a new key (line 380)

Consider explicit handling if the DB update fails to return the updated document:

 if (exists) {
   const updated = await Rooms.updateAbacAttributeValuesArrayFilteredById(rid, key, values);
   const prevValues = room.abacAttributes?.find((a) => a.key === key)?.values ?? [];

   if (this.wereAttributeValuesAdded(prevValues, values)) {
-    await this.onRoomAttributesChanged(room, updated?.abacAttributes || []);
+    if (!updated?.abacAttributes) {
+      this.logger.warn({ msg: 'Failed to retrieve updated attributes', rid, key });
+      return;
+    }
+    await this.onRoomAttributesChanged(room, updated.abacAttributes);
   }

   return;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1d0e210 and 5c3f9b9.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (15)
  • apps/meteor/app/lib/server/functions/removeUserFromRoom.ts (3 hunks)
  • apps/meteor/server/lib/dataExport/exportRoomMessagesToFile.ts (1 hunks)
  • apps/meteor/server/services/room/service.ts (2 hunks)
  • ee/packages/abac/package.json (1 hunks)
  • ee/packages/abac/src/index.spec.ts (10 hunks)
  • ee/packages/abac/src/index.ts (11 hunks)
  • ee/packages/abac/src/user-auto-removal.spec.ts (1 hunks)
  • packages/apps-engine/src/definition/messages/MessageType.ts (1 hunks)
  • packages/core-services/src/types/IRoomService.ts (2 hunks)
  • packages/core-typings/src/IMessage/IMessage.ts (1 hunks)
  • packages/http-router/src/Router.ts (1 hunks)
  • packages/i18n/src/locales/en.i18n.json (1 hunks)
  • packages/message-types/src/registrations/common.ts (1 hunks)
  • packages/model-typings/src/models/IRoomsModel.ts (1 hunks)
  • packages/models/src/models/Rooms.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/i18n/src/locales/en.i18n.json
  • packages/message-types/src/registrations/common.ts
  • packages/http-router/src/Router.ts
  • packages/model-typings/src/models/IRoomsModel.ts
  • apps/meteor/server/lib/dataExport/exportRoomMessagesToFile.ts
🧰 Additional context used
🧬 Code graph analysis (6)
apps/meteor/server/services/room/service.ts (1)
packages/core-typings/src/IMessage/IMessage.ts (1)
  • MessageTypesValues (114-114)
apps/meteor/app/lib/server/functions/removeUserFromRoom.ts (3)
apps/meteor/server/services/room/service.ts (1)
  • removeUserFromRoom (87-93)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-255)
packages/core-typings/src/IMessage/IMessage.ts (1)
  • MessageTypesValues (114-114)
packages/models/src/models/Rooms.ts (1)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (22-98)
packages/core-services/src/types/IRoomService.ts (2)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-255)
packages/core-typings/src/IMessage/IMessage.ts (1)
  • MessageTypesValues (114-114)
ee/packages/abac/src/index.ts (4)
packages/core-services/src/index.ts (2)
  • IAbacService (56-56)
  • Room (171-171)
packages/core-services/src/types/IAbacService.ts (1)
  • IAbacService (3-20)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (22-98)
packages/core-typings/src/IAbacAttribute.ts (1)
  • IAbacAttributeDefinition (3-14)
ee/packages/abac/src/user-auto-removal.spec.ts (5)
ee/packages/abac/src/index.ts (1)
  • AbacService (13-459)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (22-98)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-255)
packages/core-typings/src/IAbacAttribute.ts (1)
  • IAbacAttributeDefinition (3-14)
packages/models/src/index.ts (1)
  • registerServiceModels (231-265)
🪛 Biome (2.1.2)
ee/packages/abac/src/user-auto-removal.spec.ts

[error] 10-10: Expected an identifier, an array pattern, or an object pattern but instead found 'class'.

Expected an identifier, an array pattern, or an object pattern here.

(parse)


[error] 10-10: expected : but instead found {

Remove {

(parse)


[error] 13-13: ';' expected'

An explicit or implicit semicolon is expected here...

(parse)


[error] 13-13: ';' expected'

An explicit or implicit semicolon is expected here...

(parse)


[error] 14-14: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 15-15: await is only allowed within async functions and at the top levels of modules.

(parse)


[error] 17-17: Expected an expression but instead found '}'.

Expected an expression here.

(parse)


[error] 10-10: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (23)
packages/core-typings/src/IMessage/IMessage.ts (1)

107-107: LGTM!

The new message type 'abac-removed-user-from-room' is properly added to the MessageTypes array and will be included in the MessageTypesValues union type.

ee/packages/abac/package.json (1)

23-30: LGTM!

The dependency updates align well with the ABAC feature requirements:

  • p-limit (3.1.0) for concurrency control during bulk user removals
  • @rocket.chat/logger for enhanced logging
  • mongodb-memory-server for integration testing
packages/core-services/src/types/IRoomService.ts (1)

44-48: LGTM!

The interface extension properly supports the new ABAC functionality with optional parameters that maintain backward compatibility.

apps/meteor/app/lib/server/functions/removeUserFromRoom.ts (2)

24-35: LGTM!

The skipAppPreEvents guard correctly prevents Apps from blocking ABAC-driven removals while preserving error handling for normal flows.


48-64: LGTM!

The message handling logic properly prioritizes customSystemMessage over byUser-based messages, maintaining backward compatibility for existing callers.

ee/packages/abac/src/index.spec.ts (7)

421-433: LGTM!

Test correctly verifies that adding a new attribute key triggers onRoomAttributesChanged with the updated attribute list, including duplicate values.


435-446: LGTM!

Test correctly verifies that removing values (making requirements less strict) does not trigger re-evaluation.


448-461: LGTM!

Test correctly verifies that adding values to an existing attribute triggers re-evaluation with the updated attribute list.


538-553: LGTM!

Tests correctly verify that additions trigger the hook while removals do not, aligning with the PR's objective to re-evaluate only when requirements become more restrictive.


582-592: LGTM!

Test correctly verifies that removing an entire attribute does not trigger re-evaluation (making requirements less strict).


626-656: LGTM!

Tests correctly verify that both inserting new attributes and replacing existing ones trigger the hook with post-operation attributes.


713-742: LGTM!

Tests correctly verify that adding new attributes triggers the hook with either DB-returned or constructed attribute lists.

ee/packages/abac/src/user-auto-removal.spec.ts (1)

104-411: Excellent integration test coverage!

The test suite comprehensively validates the ABAC re-evaluation behavior across multiple scenarios:

  • New attribute additions and removals
  • Duplicate value handling
  • Multi-attribute AND semantics
  • Idempotency and no-op behavior
  • Edge cases (supersets, missing attributes, missing users)
  • Performance sanity check with 300 users

The tests properly verify both the debug logging output and actual database state changes.

ee/packages/abac/src/index.ts (10)

1-21: LGTM! Well-structured setup for concurrency and logging.

The additions are appropriate:

  • Concurrency limiter (20 operations) prevents server overload during bulk user removals
  • Logger provides observability into ABAC operations
  • Type imports (AtLeast, Users, Room) support the enhanced room lookups and removal operations

117-120: LGTM! Appropriate debug logging.


173-189: LGTM! Correctly gates re-evaluation on attribute additions.

The flow properly:

  • Captures previous attributes before update
  • Checks if new attributes were added via didAttributesChange
  • Triggers onRoomAttributesChanged only when constraints become stricter

The room projection now includes t and teamMain, which are required for the removeUserFromRoom call downstream.


263-298: LGTM! Correctly handles both new and existing attribute updates.

The method properly differentiates:

  • New keys → always re-evaluate (line 283)
  • Existing keys → re-evaluate only when values are added (line 295)

301-304: LGTM! Clean helper for detecting value additions.


307-323: LGTM! Correctly skips re-evaluation on attribute removal.

When attributes are removed, the room becomes less restrictive, so no user removal is needed. The debug logging provides visibility.


329-348: LGTM! Correctly triggers re-evaluation when adding new attribute.

Adding a new attribute key always increases restrictions, so re-evaluation is always required.


383-394: LGTM! Query correctly identifies non-compliant users.

The $not + $elemMatch + $all combination ensures that users are flagged as non-compliant if they lack any of the required attribute values for a given key.

Semantics: A user is compliant only if, for each room attribute key, they have all the required values (they may have additional values beyond the required ones).


461-461: LGTM! Enables default import syntax.


396-458: Confirm silent error handling in onRoomAttributesChanged. The method catches and logs all removal errors without propagation, whereas other callers let exceptions bubble. Verify that suppressing failures here is intentional.

@coderabbitai coderabbitai bot mentioned this pull request Dec 3, 2025
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.

5 participants