@lightdash/common 0.1946.0 → 0.1948.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/cjs/authorization/projectMemberAbility.d.ts.map +1 -1
  2. package/dist/cjs/authorization/projectMemberAbility.js +0 -4
  3. package/dist/cjs/authorization/projectMemberAbility.js.map +1 -1
  4. package/dist/cjs/authorization/roleToScopeMapping.d.ts.map +1 -1
  5. package/dist/cjs/authorization/roleToScopeMapping.js +11 -10
  6. package/dist/cjs/authorization/roleToScopeMapping.js.map +1 -1
  7. package/dist/cjs/authorization/roleToScopeParity.test.d.ts +2 -0
  8. package/dist/cjs/authorization/roleToScopeParity.test.d.ts.map +1 -0
  9. package/dist/cjs/authorization/roleToScopeParity.test.js +195 -0
  10. package/dist/cjs/authorization/roleToScopeParity.test.js.map +1 -0
  11. package/dist/cjs/authorization/scopeAbilityBuilder.d.ts.map +1 -1
  12. package/dist/cjs/authorization/scopeAbilityBuilder.js +10 -7
  13. package/dist/cjs/authorization/scopeAbilityBuilder.js.map +1 -1
  14. package/dist/cjs/authorization/scopeAbilityBuilder.test.js +312 -89
  15. package/dist/cjs/authorization/scopeAbilityBuilder.test.js.map +1 -1
  16. package/dist/cjs/authorization/scopes.d.ts.map +1 -1
  17. package/dist/cjs/authorization/scopes.js +102 -90
  18. package/dist/cjs/authorization/scopes.js.map +1 -1
  19. package/dist/cjs/index.d.ts +2 -2
  20. package/dist/cjs/index.d.ts.map +1 -1
  21. package/dist/cjs/index.js.map +1 -1
  22. package/dist/cjs/types/scopes.d.ts +2 -2
  23. package/dist/cjs/types/scopes.d.ts.map +1 -1
  24. package/dist/esm/authorization/projectMemberAbility.d.ts.map +1 -1
  25. package/dist/esm/authorization/projectMemberAbility.js +0 -4
  26. package/dist/esm/authorization/projectMemberAbility.js.map +1 -1
  27. package/dist/esm/authorization/roleToScopeMapping.d.ts.map +1 -1
  28. package/dist/esm/authorization/roleToScopeMapping.js +11 -10
  29. package/dist/esm/authorization/roleToScopeMapping.js.map +1 -1
  30. package/dist/esm/authorization/roleToScopeParity.test.d.ts +2 -0
  31. package/dist/esm/authorization/roleToScopeParity.test.d.ts.map +1 -0
  32. package/dist/esm/authorization/roleToScopeParity.test.js +193 -0
  33. package/dist/esm/authorization/roleToScopeParity.test.js.map +1 -0
  34. package/dist/esm/authorization/scopeAbilityBuilder.d.ts.map +1 -1
  35. package/dist/esm/authorization/scopeAbilityBuilder.js +10 -7
  36. package/dist/esm/authorization/scopeAbilityBuilder.js.map +1 -1
  37. package/dist/esm/authorization/scopeAbilityBuilder.test.js +312 -89
  38. package/dist/esm/authorization/scopeAbilityBuilder.test.js.map +1 -1
  39. package/dist/esm/authorization/scopes.d.ts.map +1 -1
  40. package/dist/esm/authorization/scopes.js +102 -90
  41. package/dist/esm/authorization/scopes.js.map +1 -1
  42. package/dist/esm/index.d.ts +2 -2
  43. package/dist/esm/index.d.ts.map +1 -1
  44. package/dist/esm/index.js.map +1 -1
  45. package/dist/esm/types/scopes.d.ts +2 -2
  46. package/dist/esm/types/scopes.d.ts.map +1 -1
  47. package/dist/tsconfig.types.tsbuildinfo +1 -1
  48. package/dist/types/authorization/projectMemberAbility.d.ts.map +1 -1
  49. package/dist/types/authorization/roleToScopeMapping.d.ts.map +1 -1
  50. package/dist/types/authorization/roleToScopeParity.test.d.ts +2 -0
  51. package/dist/types/authorization/roleToScopeParity.test.d.ts.map +1 -0
  52. package/dist/types/authorization/scopeAbilityBuilder.d.ts.map +1 -1
  53. package/dist/types/authorization/scopes.d.ts.map +1 -1
  54. package/dist/types/index.d.ts +2 -2
  55. package/dist/types/index.d.ts.map +1 -1
  56. package/dist/types/types/scopes.d.ts +2 -2
  57. package/dist/types/types/scopes.d.ts.map +1 -1
  58. package/package.json +1 -1
@@ -22,7 +22,7 @@ describe('scopeAbilityBuilder', () => {
22
22
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
23
23
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
24
24
  ...baseContextWithOrg,
25
- scopes: ['view:organization'],
25
+ scopes: ['view:Organization'],
26
26
  }, builder);
27
27
  const ability = builder.build();
28
28
  expect(ability.can('view', (0, ability_1.subject)('Organization', {
@@ -38,7 +38,7 @@ describe('scopeAbilityBuilder', () => {
38
38
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
39
39
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
40
40
  ...baseContext,
41
- scopes: ['view:dashboard'],
41
+ scopes: ['view:Dashboard'],
42
42
  }, builder);
43
43
  const ability = builder.build();
44
44
  // Should be able to view public dashboards
@@ -62,7 +62,7 @@ describe('scopeAbilityBuilder', () => {
62
62
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
63
63
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
64
64
  ...contextWithUser,
65
- scopes: ['view:dashboard'],
65
+ scopes: ['view:Dashboard'],
66
66
  }, builder);
67
67
  const ability = builder.build();
68
68
  // Can view dashboards with user access
@@ -80,7 +80,7 @@ describe('scopeAbilityBuilder', () => {
80
80
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
81
81
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
82
82
  ...projectContext,
83
- scopes: ['view:project'],
83
+ scopes: ['view:Project'],
84
84
  }, builder);
85
85
  const ability = builder.build();
86
86
  expect(ability.can('view', (0, ability_1.subject)('Project', {
@@ -92,7 +92,7 @@ describe('scopeAbilityBuilder', () => {
92
92
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
93
93
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
94
94
  ...baseContextWithOrg,
95
- scopes: ['create:project'],
95
+ scopes: ['create:Project'],
96
96
  }, builder);
97
97
  const ability = builder.build();
98
98
  // Can create preview projects
@@ -114,7 +114,7 @@ describe('scopeAbilityBuilder', () => {
114
114
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
115
115
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
116
116
  ...editorContext,
117
- scopes: ['manage:dashboard'],
117
+ scopes: ['manage:Dashboard'],
118
118
  }, builder);
119
119
  const ability = builder.build();
120
120
  // Can manage dashboards where user is editor
@@ -136,7 +136,7 @@ describe('scopeAbilityBuilder', () => {
136
136
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
137
137
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
138
138
  ...adminContext,
139
- scopes: ['manage:space'],
139
+ scopes: ['manage:Space'],
140
140
  }, builder);
141
141
  const ability = builder.build();
142
142
  // Can manage spaces where user is admin
@@ -158,7 +158,7 @@ describe('scopeAbilityBuilder', () => {
158
158
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
159
159
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
160
160
  ...userContext,
161
- scopes: ['view:job_status'],
161
+ scopes: ['view:JobStatus@self'],
162
162
  }, builder);
163
163
  const ability = builder.build();
164
164
  // Can view job status created by the user
@@ -179,17 +179,17 @@ describe('scopeAbilityBuilder', () => {
179
179
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
180
180
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
181
181
  ...userContext,
182
- scopes: ['manage:ai_agent_thread'],
182
+ scopes: ['manage:AiAgentThread@self'],
183
183
  }, builder);
184
184
  const ability = builder.build();
185
185
  // Can manage user's own AI agent threads
186
186
  expect(ability.can('manage', (0, ability_1.subject)('AiAgentThread', {
187
- organizationUuid: 'org-123',
187
+ projectUuid: 'project-123',
188
188
  userUuid: 'user-456',
189
189
  }))).toBe(true);
190
190
  // Cannot manage another user's threads
191
191
  expect(ability.can('manage', (0, ability_1.subject)('AiAgentThread', {
192
- organizationUuid: 'org-123',
192
+ projectUuid: 'project-123',
193
193
  userUuid: 'other-user',
194
194
  }))).toBe(false);
195
195
  });
@@ -198,7 +198,7 @@ describe('scopeAbilityBuilder', () => {
198
198
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
199
199
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
200
200
  ...baseContext,
201
- scopes: ['view:analytics', 'manage:tags'],
201
+ scopes: ['view:Analytics', 'manage:Tags'],
202
202
  }, builder);
203
203
  const ability = builder.build();
204
204
  expect(ability.can('view', (0, ability_1.subject)('Analytics', {
@@ -234,11 +234,7 @@ describe('scopeAbilityBuilder', () => {
234
234
  organizationUuid: 'org-123',
235
235
  isEnterprise: false,
236
236
  organizationRole: 'admin',
237
- scopes: [
238
- 'view:dashboard',
239
- 'manage:saved_chart',
240
- 'view:project',
241
- ],
237
+ scopes: ['view:Dashboard', 'manage:SavedChart', 'view:Project'],
242
238
  };
243
239
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
244
240
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(context, builder);
@@ -269,7 +265,7 @@ describe('scopeAbilityBuilder', () => {
269
265
  const contextWithOrgManage = {
270
266
  ...baseContext,
271
267
  userUuid: 'user-456',
272
- scopes: ['manage:organization', 'manage:saved_chart'],
268
+ scopes: ['manage:Organization', 'manage:SavedChart'],
273
269
  };
274
270
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
275
271
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithOrgManage, builder);
@@ -290,7 +286,7 @@ describe('scopeAbilityBuilder', () => {
290
286
  const contextWithoutOrgManage = {
291
287
  ...baseContext,
292
288
  userUuid: 'user-456',
293
- scopes: ['manage:saved_chart'],
289
+ scopes: ['manage:SavedChart@space'],
294
290
  };
295
291
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
296
292
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithoutOrgManage, builder);
@@ -317,7 +313,7 @@ describe('scopeAbilityBuilder', () => {
317
313
  const contextWithProjectManage = {
318
314
  ...baseContext,
319
315
  userUuid: 'user-456',
320
- scopes: ['manage:project', 'manage:space'],
316
+ scopes: ['manage:Project', 'manage:Space'],
321
317
  };
322
318
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
323
319
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithProjectManage, builder);
@@ -344,12 +340,12 @@ describe('scopeAbilityBuilder', () => {
344
340
  const contextWithOrgManage = {
345
341
  ...baseContext,
346
342
  userUuid: 'user-456',
347
- scopes: ['manage:organization', 'promote:dashboard'],
343
+ scopes: ['manage:Organization', 'promote:Dashboard'],
348
344
  };
349
345
  const contextWithoutOrgManage = {
350
346
  ...baseContext,
351
347
  userUuid: 'user-456',
352
- scopes: ['promote:dashboard'],
348
+ scopes: ['promote:Dashboard@space'],
353
349
  };
354
350
  // Test dashboard promotion with organization management
355
351
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
@@ -381,6 +377,82 @@ describe('scopeAbilityBuilder', () => {
381
377
  }))).toBe(false);
382
378
  });
383
379
  });
380
+ describe('AI agent thread permissions with modifiers', () => {
381
+ it('should handle view:ai_agent_thread@self permissions', () => {
382
+ const contextWithUser = {
383
+ ...baseContext,
384
+ userUuid: 'user-456',
385
+ isEnterprise: true,
386
+ };
387
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
388
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
389
+ ...contextWithUser,
390
+ scopes: ['view:AiAgentThread@self'],
391
+ }, builder);
392
+ const ability = builder.build();
393
+ // Can view own AI agent threads
394
+ expect(ability.can('view', (0, ability_1.subject)('AiAgentThread', {
395
+ projectUuid: 'project-123',
396
+ userUuid: 'user-456',
397
+ }))).toBe(true);
398
+ // Cannot view other users' threads
399
+ expect(ability.can('view', (0, ability_1.subject)('AiAgentThread', {
400
+ projectUuid: 'project-123',
401
+ userUuid: 'other-user',
402
+ }))).toBe(false);
403
+ });
404
+ it('should handle manage:ai_agent_thread@self permissions', () => {
405
+ const contextWithUser = {
406
+ ...baseContext,
407
+ userUuid: 'user-456',
408
+ isEnterprise: true,
409
+ };
410
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
411
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
412
+ ...contextWithUser,
413
+ userUuid: 'user-456',
414
+ scopes: ['manage:AiAgentThread@self'],
415
+ }, builder);
416
+ const ability = builder.build();
417
+ // Can manage own AI agent threads
418
+ expect(ability.can('manage', (0, ability_1.subject)('AiAgentThread', {
419
+ projectUuid: 'project-123',
420
+ userUuid: 'user-456',
421
+ }))).toBe(true);
422
+ // Cannot manage other users' threads
423
+ expect(ability.can('manage', (0, ability_1.subject)('AiAgentThread', {
424
+ userUuid: 'other-user',
425
+ }))).toBe(false);
426
+ });
427
+ it('should handle view:ai_agent_thread permissions for all threads', () => {
428
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
429
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
430
+ ...baseContextWithOrg,
431
+ isEnterprise: true,
432
+ scopes: ['view:AiAgentThread'],
433
+ }, builder);
434
+ const ability = builder.build();
435
+ // Can view any AI agent thread
436
+ expect(ability.can('view', (0, ability_1.subject)('AiAgentThread', {
437
+ organizationUuid: 'org-123',
438
+ userUuid: 'any-user',
439
+ }))).toBe(true);
440
+ });
441
+ it('should handle manage:ai_agent_thread permissions for all threads', () => {
442
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
443
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
444
+ ...baseContextWithOrg,
445
+ isEnterprise: true,
446
+ scopes: ['manage:AiAgentThread'],
447
+ }, builder);
448
+ const ability = builder.build();
449
+ // Can manage any AI agent thread
450
+ expect(ability.can('manage', (0, ability_1.subject)('AiAgentThread', {
451
+ organizationUuid: 'org-123',
452
+ userUuid: 'any-user',
453
+ }))).toBe(true);
454
+ });
455
+ });
384
456
  describe('edge cases and error handling', () => {
385
457
  it('should handle empty scope array', () => {
386
458
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
@@ -395,7 +467,7 @@ describe('scopeAbilityBuilder', () => {
395
467
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
396
468
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
397
469
  ...contextWithoutUser,
398
- scopes: ['view:dashboard'],
470
+ scopes: ['view:Dashboard'],
399
471
  }, builder);
400
472
  const ability = builder.build();
401
473
  // Should only allow viewing public dashboards
@@ -415,8 +487,8 @@ describe('scopeAbilityBuilder', () => {
415
487
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
416
488
  ...baseContext,
417
489
  scopes: [
418
- 'view:dashboard',
419
- 'view:project',
490
+ 'view:Dashboard',
491
+ 'view:Project',
420
492
  'invalid:scope',
421
493
  ],
422
494
  }, builder);
@@ -433,9 +505,9 @@ describe('scopeAbilityBuilder', () => {
433
505
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
434
506
  ...baseContext,
435
507
  scopes: [
436
- 'view:dashboard',
437
- 'manage:saved_chart',
438
- 'view:space',
508
+ 'view:Dashboard',
509
+ 'manage:SavedChart',
510
+ 'view:Space',
439
511
  ],
440
512
  }, builder);
441
513
  const ability = builder.build();
@@ -458,7 +530,7 @@ describe('scopeAbilityBuilder', () => {
458
530
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
459
531
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
460
532
  ...baseContext,
461
- scopes: ['view:saved_chart'],
533
+ scopes: ['view:SavedChart'],
462
534
  }, builder);
463
535
  const ability = builder.build();
464
536
  // Should not access saved chart from different project
@@ -478,7 +550,7 @@ describe('scopeAbilityBuilder', () => {
478
550
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
479
551
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
480
552
  ...contextWithUser,
481
- scopes: ['view:dashboard'],
553
+ scopes: ['view:Dashboard'],
482
554
  }, builder);
483
555
  const ability = builder.build();
484
556
  // Can view private dashboard with viewer access
@@ -518,7 +590,7 @@ describe('scopeAbilityBuilder', () => {
518
590
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
519
591
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
520
592
  ...contextWithUser,
521
- scopes: ['manage:dashboard'],
593
+ scopes: ['manage:Dashboard@space'],
522
594
  }, builder);
523
595
  const ability = builder.build();
524
596
  // Can manage dashboard with editor role
@@ -565,7 +637,7 @@ describe('scopeAbilityBuilder', () => {
565
637
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
566
638
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
567
639
  ...contextWithUser,
568
- scopes: ['manage:space@assigned'],
640
+ scopes: ['manage:Space@assigned'],
569
641
  }, builder);
570
642
  const ability = builder.build();
571
643
  // Can manage space with admin role
@@ -603,7 +675,7 @@ describe('scopeAbilityBuilder', () => {
603
675
  });
604
676
  });
605
677
  describe('job and job status permissions', () => {
606
- it('should handle view:job permissions', () => {
678
+ it('should handle view:job@self permissions', () => {
607
679
  const contextWithUser = {
608
680
  ...baseContext,
609
681
  userUuid: 'user-456',
@@ -611,7 +683,7 @@ describe('scopeAbilityBuilder', () => {
611
683
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
612
684
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
613
685
  ...contextWithUser,
614
- scopes: ['view:job'],
686
+ scopes: ['view:Job@self'],
615
687
  }, builder);
616
688
  const ability = builder.build();
617
689
  // Can view own jobs
@@ -623,30 +695,31 @@ describe('scopeAbilityBuilder', () => {
623
695
  userUuid: 'other-user',
624
696
  }))).toBe(false);
625
697
  });
626
- it('should handle view:job_status permissions for organization context', () => {
698
+ it('should handle view:job_status@self permissions for user context', () => {
627
699
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
628
700
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
629
701
  ...baseContext,
630
- scopes: ['view:job_status'],
702
+ userUuid: 'user-456',
703
+ scopes: ['view:JobStatus@self'],
631
704
  }, builder);
632
705
  const ability = builder.build();
633
- // Cannot view job status without user context when no manage:Organization scope
706
+ // Can view own job status
634
707
  expect(ability.can('view', (0, ability_1.subject)('JobStatus', {
635
- organizationUuid: 'org-123',
636
- }))).toBe(false);
637
- // Cannot view job status from another organization
708
+ createdByUserUuid: 'user-456',
709
+ }))).toBe(true);
710
+ // Cannot view other users' job status
638
711
  expect(ability.can('view', (0, ability_1.subject)('JobStatus', {
639
- organizationUuid: 'different-org',
712
+ createdByUserUuid: 'other-user',
640
713
  }))).toBe(false);
641
714
  });
642
- it('should handle view:job_status permissions with manage:Organization scope', () => {
715
+ it('should handle view:job_status permissions for all job status', () => {
643
716
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
644
717
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
645
718
  ...baseContextWithOrg,
646
- scopes: ['view:job_status', 'manage:organization'],
719
+ scopes: ['view:JobStatus'],
647
720
  }, builder);
648
721
  const ability = builder.build();
649
- // Can view all job status in organization when manage:Organization scope is present
722
+ // Can view all job status in organization
650
723
  expect(ability.can('view', (0, ability_1.subject)('JobStatus', {
651
724
  organizationUuid: 'org-123',
652
725
  }))).toBe(true);
@@ -655,7 +728,70 @@ describe('scopeAbilityBuilder', () => {
655
728
  organizationUuid: 'different-org',
656
729
  }))).toBe(false);
657
730
  });
658
- it('should handle view:job_status permissions for user context', () => {
731
+ it('should handle view:job permissions for all jobs', () => {
732
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
733
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
734
+ ...baseContext,
735
+ scopes: ['view:Job'],
736
+ }, builder);
737
+ const ability = builder.build();
738
+ // Can view any job
739
+ expect(ability.can('view', (0, ability_1.subject)('Job', {
740
+ organizationUuid: 'org-123',
741
+ projectUuid: 'project-123',
742
+ userUuid: 'any-user',
743
+ }))).toBe(true);
744
+ });
745
+ });
746
+ describe('space-based permissions modifiers', () => {
747
+ it('should handle manage:dashboard@space permissions', () => {
748
+ const contextWithUser = {
749
+ ...baseContextWithOrg,
750
+ userUuid: 'user-456',
751
+ };
752
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
753
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
754
+ ...contextWithUser,
755
+ scopes: ['manage:Dashboard@space'],
756
+ }, builder);
757
+ const ability = builder.build();
758
+ // Can manage dashboard with editor role
759
+ expect(ability.can('manage', (0, ability_1.subject)('Dashboard', {
760
+ organizationUuid: 'org-123',
761
+ access: [
762
+ {
763
+ userUuid: 'user-456',
764
+ role: space_1.SpaceMemberRole.EDITOR,
765
+ },
766
+ ],
767
+ }))).toBe(true);
768
+ // Can manage dashboard with admin role
769
+ expect(ability.can('manage', (0, ability_1.subject)('Dashboard', {
770
+ organizationUuid: 'org-123',
771
+ access: [
772
+ {
773
+ userUuid: 'user-456',
774
+ role: space_1.SpaceMemberRole.ADMIN,
775
+ },
776
+ ],
777
+ }))).toBe(true);
778
+ // Cannot manage dashboard with viewer role
779
+ expect(ability.can('manage', (0, ability_1.subject)('Dashboard', {
780
+ organizationUuid: 'org-123',
781
+ access: [
782
+ {
783
+ userUuid: 'user-456',
784
+ role: space_1.SpaceMemberRole.VIEWER,
785
+ },
786
+ ],
787
+ }))).toBe(false);
788
+ // Cannot manage dashboard without access
789
+ expect(ability.can('manage', (0, ability_1.subject)('Dashboard', {
790
+ organizationUuid: 'org-123',
791
+ access: [],
792
+ }))).toBe(false);
793
+ });
794
+ it('should handle manage:saved_chart@space permissions', () => {
659
795
  const contextWithUser = {
660
796
  ...baseContext,
661
797
  userUuid: 'user-456',
@@ -663,16 +799,102 @@ describe('scopeAbilityBuilder', () => {
663
799
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
664
800
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
665
801
  ...contextWithUser,
666
- scopes: ['view:job_status'],
802
+ scopes: ['manage:SavedChart@space'],
667
803
  }, builder);
668
804
  const ability = builder.build();
669
- // Can view own job status
670
- expect(ability.can('view', (0, ability_1.subject)('JobStatus', {
671
- createdByUserUuid: 'user-456',
805
+ // Can manage saved chart with editor role
806
+ expect(ability.can('manage', (0, ability_1.subject)('SavedChart', {
807
+ projectUuid: 'project-123',
808
+ access: [
809
+ {
810
+ userUuid: 'user-456',
811
+ role: space_1.SpaceMemberRole.EDITOR,
812
+ },
813
+ ],
672
814
  }))).toBe(true);
673
- // Cannot view other users' job status
674
- expect(ability.can('view', (0, ability_1.subject)('JobStatus', {
675
- createdByUserUuid: 'other-user',
815
+ // Can manage saved chart with admin role
816
+ expect(ability.can('manage', (0, ability_1.subject)('SavedChart', {
817
+ projectUuid: 'project-123',
818
+ access: [
819
+ {
820
+ userUuid: 'user-456',
821
+ role: space_1.SpaceMemberRole.ADMIN,
822
+ },
823
+ ],
824
+ }))).toBe(true);
825
+ // Cannot manage without proper access
826
+ expect(ability.can('manage', (0, ability_1.subject)('SavedChart', {
827
+ projectUuid: 'project-123',
828
+ access: [
829
+ {
830
+ userUuid: 'other-user',
831
+ role: space_1.SpaceMemberRole.EDITOR,
832
+ },
833
+ ],
834
+ }))).toBe(false);
835
+ });
836
+ it('should handle promote:dashboard@space permissions', () => {
837
+ const contextWithUser = {
838
+ ...baseContext,
839
+ userUuid: 'user-456',
840
+ };
841
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
842
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
843
+ ...contextWithUser,
844
+ scopes: ['promote:Dashboard@space'],
845
+ }, builder);
846
+ const ability = builder.build();
847
+ // Can promote dashboard with editor access
848
+ expect(ability.can('promote', (0, ability_1.subject)('Dashboard', {
849
+ projectUuid: 'project-123',
850
+ access: [
851
+ {
852
+ userUuid: 'user-456',
853
+ role: space_1.SpaceMemberRole.EDITOR,
854
+ },
855
+ ],
856
+ }))).toBe(true);
857
+ // Cannot promote without editor access
858
+ expect(ability.can('promote', (0, ability_1.subject)('Dashboard', {
859
+ projectUuid: 'project-123',
860
+ access: [
861
+ {
862
+ userUuid: 'user-456',
863
+ role: space_1.SpaceMemberRole.VIEWER,
864
+ },
865
+ ],
866
+ }))).toBe(false);
867
+ });
868
+ it('should handle manage:semantic_viewer@space permissions', () => {
869
+ const contextWithUser = {
870
+ ...baseContextWithOrg,
871
+ userUuid: 'user-456',
872
+ };
873
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
874
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
875
+ ...contextWithUser,
876
+ scopes: ['manage:SemanticViewer@space'],
877
+ }, builder);
878
+ const ability = builder.build();
879
+ // Can manage semantic viewer with editor role
880
+ expect(ability.can('manage', (0, ability_1.subject)('SemanticViewer', {
881
+ organizationUuid: 'org-123',
882
+ access: [
883
+ {
884
+ userUuid: 'user-456',
885
+ role: space_1.SpaceMemberRole.EDITOR,
886
+ },
887
+ ],
888
+ }))).toBe(true);
889
+ // Cannot manage without editor role
890
+ expect(ability.can('manage', (0, ability_1.subject)('SemanticViewer', {
891
+ organizationUuid: 'org-123',
892
+ access: [
893
+ {
894
+ userUuid: 'user-456',
895
+ role: space_1.SpaceMemberRole.VIEWER,
896
+ },
897
+ ],
676
898
  }))).toBe(false);
677
899
  });
678
900
  });
@@ -681,7 +903,7 @@ describe('scopeAbilityBuilder', () => {
681
903
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
682
904
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
683
905
  ...baseContext,
684
- scopes: ['view:semantic_viewer'],
906
+ scopes: ['view:SemanticViewer'],
685
907
  }, builder);
686
908
  const ability = builder.build();
687
909
  expect(ability.can('view', (0, ability_1.subject)('SemanticViewer', {
@@ -693,14 +915,14 @@ describe('scopeAbilityBuilder', () => {
693
915
  const contextWithOrgManage = {
694
916
  ...baseContextWithOrg,
695
917
  userUuid: 'user-456',
696
- scopes: ['manage:organization'],
918
+ scopes: ['manage:Organization'],
697
919
  };
698
920
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
699
921
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
700
922
  ...contextWithOrgManage,
701
923
  scopes: [
702
- 'manage:organization',
703
- 'manage:semantic_viewer',
924
+ 'manage:Organization',
925
+ 'manage:SemanticViewer',
704
926
  ],
705
927
  }, builder);
706
928
  const ability = builder.build();
@@ -717,7 +939,7 @@ describe('scopeAbilityBuilder', () => {
717
939
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
718
940
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
719
941
  ...contextWithUser,
720
- scopes: ['manage:semantic_viewer'],
942
+ scopes: ['manage:SemanticViewer@space'],
721
943
  }, builder);
722
944
  const ability = builder.build();
723
945
  // Can manage semantic viewer with editor role
@@ -747,7 +969,7 @@ describe('scopeAbilityBuilder', () => {
747
969
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
748
970
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
749
971
  ...baseContext,
750
- scopes: ['create:space'],
972
+ scopes: ['create:Space'],
751
973
  }, builder);
752
974
  const ability = builder.build();
753
975
  expect(ability.can('create', (0, ability_1.subject)('Space', {
@@ -761,7 +983,7 @@ describe('scopeAbilityBuilder', () => {
761
983
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
762
984
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
763
985
  ...baseContext,
764
- scopes: ['manage:export_csv'],
986
+ scopes: ['manage:ExportCsv'],
765
987
  }, builder);
766
988
  const ability = builder.build();
767
989
  expect(ability.can('manage', (0, ability_1.subject)('ExportCsv', {
@@ -773,7 +995,7 @@ describe('scopeAbilityBuilder', () => {
773
995
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
774
996
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
775
997
  ...baseContext,
776
- scopes: ['manage:change_csv_results'],
998
+ scopes: ['manage:ChangeCsvResults'],
777
999
  }, builder);
778
1000
  const ability = builder.build();
779
1001
  expect(ability.can('manage', (0, ability_1.subject)('ChangeCsvResults', {
@@ -787,7 +1009,7 @@ describe('scopeAbilityBuilder', () => {
787
1009
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
788
1010
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
789
1011
  ...baseContext,
790
- scopes: ['view:underlying_data'],
1012
+ scopes: ['view:UnderlyingData'],
791
1013
  }, builder);
792
1014
  const ability = builder.build();
793
1015
  expect(ability.can('view', (0, ability_1.subject)('UnderlyingData', {
@@ -801,7 +1023,7 @@ describe('scopeAbilityBuilder', () => {
801
1023
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
802
1024
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
803
1025
  ...baseContext,
804
- scopes: ['manage:sql_runner'],
1026
+ scopes: ['manage:SqlRunner'],
805
1027
  }, builder);
806
1028
  const ability = builder.build();
807
1029
  expect(ability.can('manage', (0, ability_1.subject)('SqlRunner', {
@@ -813,7 +1035,7 @@ describe('scopeAbilityBuilder', () => {
813
1035
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
814
1036
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
815
1037
  ...baseContext,
816
- scopes: ['manage:custom_sql'],
1038
+ scopes: ['manage:CustomSql'],
817
1039
  }, builder);
818
1040
  const ability = builder.build();
819
1041
  expect(ability.can('manage', (0, ability_1.subject)('CustomSql', {
@@ -828,7 +1050,7 @@ describe('scopeAbilityBuilder', () => {
828
1050
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
829
1051
  ...baseContext,
830
1052
  userUuid: 'user-456',
831
- scopes: ['delete:project@self'],
1053
+ scopes: ['delete:Project@self'],
832
1054
  }, builder);
833
1055
  const ability = builder.build();
834
1056
  // Can delete specific project
@@ -841,21 +1063,22 @@ describe('scopeAbilityBuilder', () => {
841
1063
  type: projects_1.ProjectType.PREVIEW,
842
1064
  }))).toBe(false);
843
1065
  });
844
- it('should handle delete:project for preview projects', () => {
1066
+ it('should handle delete:project@self for own preview projects', () => {
845
1067
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
846
1068
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
847
- ...baseContextWithOrg,
848
- scopes: ['delete:project'],
1069
+ ...baseContext,
1070
+ userUuid: 'user-456',
1071
+ scopes: ['delete:Project@self'],
849
1072
  }, builder);
850
1073
  const ability = builder.build();
851
- // Can delete preview projects in organization
1074
+ // Can delete preview projects in a project
852
1075
  expect(ability.can('delete', (0, ability_1.subject)('Project', {
853
- organizationUuid: 'org-123',
1076
+ createdByUserUuid: 'user-456',
854
1077
  type: projects_1.ProjectType.PREVIEW,
855
1078
  }))).toBe(true);
856
1079
  // Cannot delete default projects
857
1080
  expect(ability.can('delete', (0, ability_1.subject)('Project', {
858
- organizationUuid: 'org-123',
1081
+ createdByUserUuid: 'user-456',
859
1082
  type: projects_1.ProjectType.DEFAULT,
860
1083
  }))).toBe(false);
861
1084
  });
@@ -865,7 +1088,7 @@ describe('scopeAbilityBuilder', () => {
865
1088
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
866
1089
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
867
1090
  ...baseContext,
868
- scopes: ['view:pinned_items'],
1091
+ scopes: ['view:PinnedItems'],
869
1092
  }, builder);
870
1093
  const ability = builder.build();
871
1094
  expect(ability.can('view', (0, ability_1.subject)('PinnedItems', {
@@ -877,7 +1100,7 @@ describe('scopeAbilityBuilder', () => {
877
1100
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
878
1101
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
879
1102
  ...baseContext,
880
- scopes: ['manage:pinned_items'],
1103
+ scopes: ['manage:PinnedItems'],
881
1104
  }, builder);
882
1105
  const ability = builder.build();
883
1106
  expect(ability.can('manage', (0, ability_1.subject)('PinnedItems', {
@@ -891,7 +1114,7 @@ describe('scopeAbilityBuilder', () => {
891
1114
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
892
1115
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
893
1116
  ...baseContext,
894
- scopes: ['manage:explore'],
1117
+ scopes: ['manage:Explore'],
895
1118
  }, builder);
896
1119
  const ability = builder.build();
897
1120
  expect(ability.can('manage', (0, ability_1.subject)('Explore', {
@@ -905,7 +1128,7 @@ describe('scopeAbilityBuilder', () => {
905
1128
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
906
1129
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
907
1130
  ...baseContext,
908
- scopes: ['create:virtual_view'],
1131
+ scopes: ['create:VirtualView'],
909
1132
  }, builder);
910
1133
  const ability = builder.build();
911
1134
  expect(ability.can('create', (0, ability_1.subject)('VirtualView', {
@@ -922,7 +1145,7 @@ describe('scopeAbilityBuilder', () => {
922
1145
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
923
1146
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
924
1147
  ...baseContext,
925
- scopes: ['delete:virtual_view'],
1148
+ scopes: ['delete:VirtualView'],
926
1149
  }, builder);
927
1150
  const ability = builder.build();
928
1151
  expect(ability.can('delete', (0, ability_1.subject)('VirtualView', {
@@ -939,7 +1162,7 @@ describe('scopeAbilityBuilder', () => {
939
1162
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
940
1163
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
941
1164
  ...baseContext,
942
- scopes: ['manage:virtual_view'],
1165
+ scopes: ['manage:VirtualView'],
943
1166
  }, builder);
944
1167
  const ability = builder.build();
945
1168
  // Should be able to manage (create and delete)
@@ -962,9 +1185,9 @@ describe('scopeAbilityBuilder', () => {
962
1185
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
963
1186
  ...baseContextWithOrg,
964
1187
  scopes: [
965
- 'create:virtual_view',
966
- 'delete:virtual_view',
967
- 'manage:virtual_view',
1188
+ 'create:VirtualView',
1189
+ 'delete:VirtualView',
1190
+ 'manage:VirtualView',
968
1191
  ],
969
1192
  }, builder);
970
1193
  const ability = builder.build();
@@ -987,9 +1210,9 @@ describe('scopeAbilityBuilder', () => {
987
1210
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
988
1211
  ...baseContextWithOrg,
989
1212
  scopes: [
990
- 'create:virtual_view',
991
- 'delete:virtual_view',
992
- 'manage:virtual_view',
1213
+ 'create:VirtualView',
1214
+ 'delete:VirtualView',
1215
+ 'manage:VirtualView',
993
1216
  ],
994
1217
  }, builder);
995
1218
  const ability = builder.build();
@@ -1014,7 +1237,7 @@ describe('scopeAbilityBuilder', () => {
1014
1237
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
1015
1238
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
1016
1239
  ...baseContextWithOrg,
1017
- scopes: ['view:organization_member_profile'],
1240
+ scopes: ['view:OrganizationMemberProfile'],
1018
1241
  }, builder);
1019
1242
  const ability = builder.build();
1020
1243
  expect(ability.can('view', (0, ability_1.subject)('OrganizationMemberProfile', {
@@ -1031,7 +1254,7 @@ describe('scopeAbilityBuilder', () => {
1031
1254
  const builder = new ability_1.AbilityBuilder(ability_1.Ability);
1032
1255
  (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
1033
1256
  ...baseContext,
1034
- scopes: ['manage:organization_member_profile'],
1257
+ scopes: ['manage:OrganizationMemberProfile'],
1035
1258
  }, builder);
1036
1259
  const ability = builder.build();
1037
1260
  expect(ability.can('manage', (0, ability_1.subject)('OrganizationMemberProfile', {
@@ -1047,7 +1270,7 @@ describe('scopeAbilityBuilder', () => {
1047
1270
  ...baseContext,
1048
1271
  isEnterprise: true,
1049
1272
  organizationRole: 'admin',
1050
- scopes: ['manage:personal_access_token'],
1273
+ scopes: ['manage:PersonalAccessToken'],
1051
1274
  permissionsConfig: {
1052
1275
  pat: {
1053
1276
  enabled: true,
@@ -1064,7 +1287,7 @@ describe('scopeAbilityBuilder', () => {
1064
1287
  ...baseContext,
1065
1288
  isEnterprise: true,
1066
1289
  organizationRole: 'admin',
1067
- scopes: ['manage:personal_access_token'],
1290
+ scopes: ['manage:PersonalAccessToken'],
1068
1291
  permissionsConfig: {
1069
1292
  pat: {
1070
1293
  enabled: false,
@@ -1081,7 +1304,7 @@ describe('scopeAbilityBuilder', () => {
1081
1304
  ...baseContext,
1082
1305
  isEnterprise: true,
1083
1306
  organizationRole: 'developer',
1084
- scopes: ['manage:personal_access_token'],
1307
+ scopes: ['manage:PersonalAccessToken'],
1085
1308
  permissionsConfig: {
1086
1309
  pat: {
1087
1310
  enabled: true,
@@ -1098,7 +1321,7 @@ describe('scopeAbilityBuilder', () => {
1098
1321
  ...baseContext,
1099
1322
  isEnterprise: true,
1100
1323
  organizationRole: 'admin',
1101
- scopes: ['manage:personal_access_token'],
1324
+ scopes: ['manage:PersonalAccessToken'],
1102
1325
  // No permissionsConfig provided
1103
1326
  }, builder);
1104
1327
  const ability = builder.build();
@@ -1110,7 +1333,7 @@ describe('scopeAbilityBuilder', () => {
1110
1333
  ...baseContext,
1111
1334
  isEnterprise: true,
1112
1335
  organizationRole: '', // Empty organization role
1113
- scopes: ['manage:personal_access_token'],
1336
+ scopes: ['manage:PersonalAccessToken'],
1114
1337
  permissionsConfig: {
1115
1338
  pat: {
1116
1339
  enabled: true,