fix(datatrak): RN-1819: detect descendant changes in permission group hierarchy#6708
fix(datatrak): RN-1819: detect descendant changes in permission group hierarchy#6708
Conversation
| WITH RECURSIVE tree AS ( | ||
| SELECT id, parent_id, 1 AS depth | ||
| FROM permission_group | ||
| WHERE parent_id IS NULL | ||
|
|
||
| UNION ALL | ||
|
|
||
| SELECT pg.id, pg.parent_id, t.depth + 1 | ||
| FROM permission_group pg | ||
| INNER JOIN tree t ON pg.parent_id = t.id | ||
| ) | ||
| SELECT max(depth) + :headroom AS height | ||
| FROM tree |
There was a problem hiding this comment.
As of today, the maximum permission group tree height is 7
|
🦸 Review Hero (could not post inline comments — showing here instead)
[Bugs & Correctness]
[Bugs & Correctness] The recursive walk CTE uses
[Design & Architecture] The two async wrapper lambdas exist solely to defer evaluation for short-circuit if (
await hasSyncSnapshotRecords(this.database, sessionId, undefined, this.models.userEntityPermission.databaseRecord, "data->>'user_id' = :userId", { userId: currentUserId }) ||
await hasPermissionGroupHierarchyChangeInSyncSnapshot(this.database, sessionId, currentUserId)
) {The wrappers add 12 lines of indirection with no benefit — lambdas wrapping a single awaited call is the same as just calling the function.
[Design & Architecture] The changed_pgs AS (
SELECT 1 FROM ${snapshotTable} WHERE record_type = :recordType LIMIT 1
),and gate
[Performance] The LATERAL subquery inside the recursive |
|
🦸 Review Hero Summary Below consensus threshold (4 unique issues not confirmed by majority)
Local fix prompt (copy to your coding agent)Fix these issues identified on the pull request. One commit per issue fixed.
The wrappers add 12 lines of indirection with no benefit — lambdas wrapping a single awaited call is the same as just calling the function.
and gate
|
@chris-bes Confident Review Hero is wrong about - WHERE w.depth < :maxDepth
+ WHERE w.depth < (
+ WITH RECURSIVE tree AS (
+ SELECT id, parent_id, 1 AS depth
+ FROM permission_group
+ WHERE parent_id IS NULL
+
+ UNION ALL
+
+ SELECT pg.id, pg.parent_id, t.depth + 1
+ FROM permission_group pg
+ INNER JOIN tree t ON pg.parent_id = t.id
+ )
+ SELECT max(depth) + :headroom AS max_recursion_depth
+ FROM tree
+ ) |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c1a1ec2. Configure here.
| FROM tree | ||
| )`, | ||
| { headroom: 10 }, | ||
| ); |
There was a problem hiding this comment.
Raw builder object used as named binding value
High Severity
maxDepth is assigned a Knex Raw builder (the unawaited return of database.connection.raw(...)) and then passed as a value inside a named-bindings object to executeSql. While Knex supports QueryBuilder/Raw instances as positional bindings (array elements — as seen in BaseDatabase.exists()), its support for Raw instances as values in named-binding objects (:maxDepth) is undocumented and has no precedent in this codebase. If the named-binding path doesn't inline the Raw object, it would serialize as [object Object], causing a PostgreSQL type error or incorrect comparison in w.depth < :maxDepth. A safer approach is to inline the subquery SQL directly into the main query string or use a hardcoded constant.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit c1a1ec2. Configure here.


RN-1819
🦸 Review Hero