@amityco/social-plus-vise 0.4.0 → 0.7.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.
@@ -0,0 +1,1286 @@
1
+ {
2
+ "domain": "moderation",
3
+ "schema_version": 1,
4
+ "rules": [
5
+ {
6
+ "id": "typescript.moderation.report-flow-present",
7
+ "version": 1,
8
+ "title": "TypeScript moderation must include a report flow",
9
+ "severity": "warning",
10
+ "rationale": "Content moderation requires a user-facing report flow with confirmation to comply with platform safety requirements.",
11
+ "applies_when": {
12
+ "platforms": [
13
+ "typescript"
14
+ ],
15
+ "outcomes": [
16
+ "add-moderation",
17
+ "validate-setup"
18
+ ]
19
+ },
20
+ "enforcement": {
21
+ "deterministic": [
22
+ {
23
+ "check": "validator-finding-absent",
24
+ "finding_rule_id": "typescript.moderation.report-flow-present"
25
+ }
26
+ ],
27
+ "attestation": {
28
+ "allowed": true,
29
+ "host_agent_min_confidence": "high",
30
+ "human_allowed": true,
31
+ "evidence_required": [
32
+ {
33
+ "field": "report_flow_location",
34
+ "description": "Where the report flow is implemented.",
35
+ "upload_policy": "upload-with-consent"
36
+ }
37
+ ]
38
+ }
39
+ }
40
+ },
41
+ {
42
+ "id": "react-native.moderation.report-flow-present",
43
+ "version": 1,
44
+ "title": "React Native moderation must include a report flow",
45
+ "severity": "warning",
46
+ "rationale": "Content moderation requires a user-facing report flow with confirmation to comply with platform safety requirements.",
47
+ "applies_when": {
48
+ "platforms": [
49
+ "react-native"
50
+ ],
51
+ "outcomes": [
52
+ "add-moderation",
53
+ "validate-setup"
54
+ ]
55
+ },
56
+ "enforcement": {
57
+ "deterministic": [
58
+ {
59
+ "check": "validator-finding-absent",
60
+ "finding_rule_id": "react-native.moderation.report-flow-present"
61
+ }
62
+ ],
63
+ "attestation": {
64
+ "allowed": true,
65
+ "host_agent_min_confidence": "high",
66
+ "human_allowed": true,
67
+ "evidence_required": [
68
+ {
69
+ "field": "report_flow_location",
70
+ "description": "Where the report flow is implemented.",
71
+ "upload_policy": "upload-with-consent"
72
+ }
73
+ ]
74
+ }
75
+ }
76
+ },
77
+ {
78
+ "id": "android.moderation.report-flow-present",
79
+ "version": 1,
80
+ "title": "Android moderation must include a report flow",
81
+ "severity": "warning",
82
+ "rationale": "Content moderation requires a user-facing report flow with confirmation to comply with platform safety requirements.",
83
+ "applies_when": {
84
+ "platforms": [
85
+ "android"
86
+ ],
87
+ "outcomes": [
88
+ "add-moderation",
89
+ "validate-setup"
90
+ ]
91
+ },
92
+ "enforcement": {
93
+ "deterministic": [
94
+ {
95
+ "check": "validator-finding-absent",
96
+ "finding_rule_id": "android.moderation.report-flow-present"
97
+ }
98
+ ],
99
+ "attestation": {
100
+ "allowed": true,
101
+ "host_agent_min_confidence": "high",
102
+ "human_allowed": true,
103
+ "evidence_required": [
104
+ {
105
+ "field": "report_flow_location",
106
+ "description": "Where the report flow is implemented.",
107
+ "upload_policy": "upload-with-consent"
108
+ }
109
+ ]
110
+ }
111
+ }
112
+ },
113
+ {
114
+ "id": "flutter.moderation.report-flow-present",
115
+ "version": 1,
116
+ "title": "Flutter moderation must include a report flow",
117
+ "severity": "warning",
118
+ "rationale": "Content moderation requires a user-facing report flow with confirmation to comply with platform safety requirements.",
119
+ "applies_when": {
120
+ "platforms": [
121
+ "flutter"
122
+ ],
123
+ "outcomes": [
124
+ "add-moderation",
125
+ "validate-setup"
126
+ ]
127
+ },
128
+ "enforcement": {
129
+ "deterministic": [
130
+ {
131
+ "check": "validator-finding-absent",
132
+ "finding_rule_id": "flutter.moderation.report-flow-present"
133
+ }
134
+ ],
135
+ "attestation": {
136
+ "allowed": true,
137
+ "host_agent_min_confidence": "high",
138
+ "human_allowed": true,
139
+ "evidence_required": [
140
+ {
141
+ "field": "report_flow_location",
142
+ "description": "Where the report flow is implemented.",
143
+ "upload_policy": "upload-with-consent"
144
+ }
145
+ ]
146
+ }
147
+ }
148
+ },
149
+ {
150
+ "id": "ios.moderation.report-flow-present",
151
+ "version": 1,
152
+ "title": "iOS moderation must include a report flow",
153
+ "severity": "warning",
154
+ "rationale": "Content moderation requires a user-facing report flow with confirmation to comply with platform safety requirements.",
155
+ "applies_when": {
156
+ "platforms": [
157
+ "ios"
158
+ ],
159
+ "outcomes": [
160
+ "add-moderation",
161
+ "validate-setup"
162
+ ]
163
+ },
164
+ "enforcement": {
165
+ "deterministic": [
166
+ {
167
+ "check": "validator-finding-absent",
168
+ "finding_rule_id": "ios.moderation.report-flow-present"
169
+ }
170
+ ],
171
+ "attestation": {
172
+ "allowed": true,
173
+ "host_agent_min_confidence": "high",
174
+ "human_allowed": true,
175
+ "evidence_required": [
176
+ {
177
+ "field": "report_flow_location",
178
+ "description": "Where the report flow is implemented.",
179
+ "upload_policy": "upload-with-consent"
180
+ }
181
+ ]
182
+ }
183
+ }
184
+ },
185
+ {
186
+ "id": "typescript.moderation.block-or-mute-state-applied",
187
+ "version": 1,
188
+ "title": "TypeScript block/mute must apply state to rendered content",
189
+ "severity": "warning",
190
+ "rationale": "Block/mute actions must visibly affect the UI so users see the result of their moderation action.",
191
+ "applies_when": {
192
+ "platforms": [
193
+ "typescript"
194
+ ],
195
+ "outcomes": [
196
+ "add-moderation",
197
+ "validate-setup"
198
+ ]
199
+ },
200
+ "enforcement": {
201
+ "deterministic": [
202
+ {
203
+ "check": "validator-finding-absent",
204
+ "finding_rule_id": "typescript.moderation.block-or-mute-state-applied"
205
+ }
206
+ ],
207
+ "attestation": {
208
+ "allowed": true,
209
+ "host_agent_min_confidence": "medium",
210
+ "human_allowed": true,
211
+ "evidence_required": [
212
+ {
213
+ "field": "state_change_rendering",
214
+ "description": "How blocked/muted state is reflected in UI.",
215
+ "upload_policy": "upload-with-consent"
216
+ }
217
+ ]
218
+ }
219
+ }
220
+ },
221
+ {
222
+ "id": "react-native.moderation.block-or-mute-state-applied",
223
+ "version": 1,
224
+ "title": "React Native block/mute must apply state to rendered content",
225
+ "severity": "warning",
226
+ "rationale": "Block/mute actions must visibly affect the UI so users see the result of their moderation action.",
227
+ "applies_when": {
228
+ "platforms": [
229
+ "react-native"
230
+ ],
231
+ "outcomes": [
232
+ "add-moderation",
233
+ "validate-setup"
234
+ ]
235
+ },
236
+ "enforcement": {
237
+ "deterministic": [
238
+ {
239
+ "check": "validator-finding-absent",
240
+ "finding_rule_id": "react-native.moderation.block-or-mute-state-applied"
241
+ }
242
+ ],
243
+ "attestation": {
244
+ "allowed": true,
245
+ "host_agent_min_confidence": "medium",
246
+ "human_allowed": true,
247
+ "evidence_required": [
248
+ {
249
+ "field": "state_change_rendering",
250
+ "description": "How blocked/muted state is reflected in UI.",
251
+ "upload_policy": "upload-with-consent"
252
+ }
253
+ ]
254
+ }
255
+ }
256
+ },
257
+ {
258
+ "id": "android.moderation.block-or-mute-state-applied",
259
+ "version": 1,
260
+ "title": "Android block/mute must apply state to rendered content",
261
+ "severity": "warning",
262
+ "rationale": "Block/mute actions must visibly affect the UI so users see the result of their moderation action.",
263
+ "applies_when": {
264
+ "platforms": [
265
+ "android"
266
+ ],
267
+ "outcomes": [
268
+ "add-moderation",
269
+ "validate-setup"
270
+ ]
271
+ },
272
+ "enforcement": {
273
+ "deterministic": [
274
+ {
275
+ "check": "validator-finding-absent",
276
+ "finding_rule_id": "android.moderation.block-or-mute-state-applied"
277
+ }
278
+ ],
279
+ "attestation": {
280
+ "allowed": true,
281
+ "host_agent_min_confidence": "medium",
282
+ "human_allowed": true,
283
+ "evidence_required": [
284
+ {
285
+ "field": "state_change_rendering",
286
+ "description": "How blocked/muted state is reflected in UI.",
287
+ "upload_policy": "upload-with-consent"
288
+ }
289
+ ]
290
+ }
291
+ }
292
+ },
293
+ {
294
+ "id": "flutter.moderation.block-or-mute-state-applied",
295
+ "version": 1,
296
+ "title": "Flutter block/mute must apply state to rendered content",
297
+ "severity": "warning",
298
+ "rationale": "Block/mute actions must visibly affect the UI so users see the result of their moderation action.",
299
+ "applies_when": {
300
+ "platforms": [
301
+ "flutter"
302
+ ],
303
+ "outcomes": [
304
+ "add-moderation",
305
+ "validate-setup"
306
+ ]
307
+ },
308
+ "enforcement": {
309
+ "deterministic": [
310
+ {
311
+ "check": "validator-finding-absent",
312
+ "finding_rule_id": "flutter.moderation.block-or-mute-state-applied"
313
+ }
314
+ ],
315
+ "attestation": {
316
+ "allowed": true,
317
+ "host_agent_min_confidence": "medium",
318
+ "human_allowed": true,
319
+ "evidence_required": [
320
+ {
321
+ "field": "state_change_rendering",
322
+ "description": "How blocked/muted state is reflected in UI.",
323
+ "upload_policy": "upload-with-consent"
324
+ }
325
+ ]
326
+ }
327
+ }
328
+ },
329
+ {
330
+ "id": "ios.moderation.block-or-mute-state-applied",
331
+ "version": 1,
332
+ "title": "iOS block/mute must apply state to rendered content",
333
+ "severity": "warning",
334
+ "rationale": "Block/mute actions must visibly affect the UI so users see the result of their moderation action.",
335
+ "applies_when": {
336
+ "platforms": [
337
+ "ios"
338
+ ],
339
+ "outcomes": [
340
+ "add-moderation",
341
+ "validate-setup"
342
+ ]
343
+ },
344
+ "enforcement": {
345
+ "deterministic": [
346
+ {
347
+ "check": "validator-finding-absent",
348
+ "finding_rule_id": "ios.moderation.block-or-mute-state-applied"
349
+ }
350
+ ],
351
+ "attestation": {
352
+ "allowed": true,
353
+ "host_agent_min_confidence": "medium",
354
+ "human_allowed": true,
355
+ "evidence_required": [
356
+ {
357
+ "field": "state_change_rendering",
358
+ "description": "How blocked/muted state is reflected in UI.",
359
+ "upload_policy": "upload-with-consent"
360
+ }
361
+ ]
362
+ }
363
+ }
364
+ },
365
+ {
366
+ "id": "typescript.moderation.hidden-content-rendering-present",
367
+ "version": 1,
368
+ "title": "TypeScript must render hidden/blocked content appropriately",
369
+ "severity": "warning",
370
+ "rationale": "Hidden/blocked content must have explicit rendering (placeholder, removed, or blurred) rather than silently disappearing.",
371
+ "applies_when": {
372
+ "platforms": [
373
+ "typescript"
374
+ ],
375
+ "outcomes": [
376
+ "add-moderation",
377
+ "validate-setup"
378
+ ]
379
+ },
380
+ "enforcement": {
381
+ "deterministic": [
382
+ {
383
+ "check": "validator-finding-absent",
384
+ "finding_rule_id": "typescript.moderation.hidden-content-rendering-present"
385
+ }
386
+ ],
387
+ "attestation": {
388
+ "allowed": true,
389
+ "host_agent_min_confidence": "medium",
390
+ "human_allowed": true,
391
+ "evidence_required": [
392
+ {
393
+ "field": "rendering_policy",
394
+ "description": "How hidden/blocked content is rendered.",
395
+ "upload_policy": "upload-with-consent"
396
+ }
397
+ ]
398
+ }
399
+ }
400
+ },
401
+ {
402
+ "id": "react-native.moderation.hidden-content-rendering-present",
403
+ "version": 1,
404
+ "title": "React Native must render hidden/blocked content appropriately",
405
+ "severity": "warning",
406
+ "rationale": "Hidden/blocked content must have explicit rendering (placeholder, removed, or blurred) rather than silently disappearing.",
407
+ "applies_when": {
408
+ "platforms": [
409
+ "react-native"
410
+ ],
411
+ "outcomes": [
412
+ "add-moderation",
413
+ "validate-setup"
414
+ ]
415
+ },
416
+ "enforcement": {
417
+ "deterministic": [
418
+ {
419
+ "check": "validator-finding-absent",
420
+ "finding_rule_id": "react-native.moderation.hidden-content-rendering-present"
421
+ }
422
+ ],
423
+ "attestation": {
424
+ "allowed": true,
425
+ "host_agent_min_confidence": "medium",
426
+ "human_allowed": true,
427
+ "evidence_required": [
428
+ {
429
+ "field": "rendering_policy",
430
+ "description": "How hidden/blocked content is rendered.",
431
+ "upload_policy": "upload-with-consent"
432
+ }
433
+ ]
434
+ }
435
+ }
436
+ },
437
+ {
438
+ "id": "android.moderation.hidden-content-rendering-present",
439
+ "version": 1,
440
+ "title": "Android must render hidden/blocked content appropriately",
441
+ "severity": "warning",
442
+ "rationale": "Hidden/blocked content must have explicit rendering (placeholder, removed, or blurred) rather than silently disappearing.",
443
+ "applies_when": {
444
+ "platforms": [
445
+ "android"
446
+ ],
447
+ "outcomes": [
448
+ "add-moderation",
449
+ "validate-setup"
450
+ ]
451
+ },
452
+ "enforcement": {
453
+ "deterministic": [
454
+ {
455
+ "check": "validator-finding-absent",
456
+ "finding_rule_id": "android.moderation.hidden-content-rendering-present"
457
+ }
458
+ ],
459
+ "attestation": {
460
+ "allowed": true,
461
+ "host_agent_min_confidence": "medium",
462
+ "human_allowed": true,
463
+ "evidence_required": [
464
+ {
465
+ "field": "rendering_policy",
466
+ "description": "How hidden/blocked content is rendered.",
467
+ "upload_policy": "upload-with-consent"
468
+ }
469
+ ]
470
+ }
471
+ }
472
+ },
473
+ {
474
+ "id": "flutter.moderation.hidden-content-rendering-present",
475
+ "version": 1,
476
+ "title": "Flutter must render hidden/blocked content appropriately",
477
+ "severity": "warning",
478
+ "rationale": "Hidden/blocked content must have explicit rendering (placeholder, removed, or blurred) rather than silently disappearing.",
479
+ "applies_when": {
480
+ "platforms": [
481
+ "flutter"
482
+ ],
483
+ "outcomes": [
484
+ "add-moderation",
485
+ "validate-setup"
486
+ ]
487
+ },
488
+ "enforcement": {
489
+ "deterministic": [
490
+ {
491
+ "check": "validator-finding-absent",
492
+ "finding_rule_id": "flutter.moderation.hidden-content-rendering-present"
493
+ }
494
+ ],
495
+ "attestation": {
496
+ "allowed": true,
497
+ "host_agent_min_confidence": "medium",
498
+ "human_allowed": true,
499
+ "evidence_required": [
500
+ {
501
+ "field": "rendering_policy",
502
+ "description": "How hidden/blocked content is rendered.",
503
+ "upload_policy": "upload-with-consent"
504
+ }
505
+ ]
506
+ }
507
+ }
508
+ },
509
+ {
510
+ "id": "ios.moderation.hidden-content-rendering-present",
511
+ "version": 1,
512
+ "title": "iOS must render hidden/blocked content appropriately",
513
+ "severity": "warning",
514
+ "rationale": "Hidden/blocked content must have explicit rendering (placeholder, removed, or blurred) rather than silently disappearing.",
515
+ "applies_when": {
516
+ "platforms": [
517
+ "ios"
518
+ ],
519
+ "outcomes": [
520
+ "add-moderation",
521
+ "validate-setup"
522
+ ]
523
+ },
524
+ "enforcement": {
525
+ "deterministic": [
526
+ {
527
+ "check": "validator-finding-absent",
528
+ "finding_rule_id": "ios.moderation.hidden-content-rendering-present"
529
+ }
530
+ ],
531
+ "attestation": {
532
+ "allowed": true,
533
+ "host_agent_min_confidence": "medium",
534
+ "human_allowed": true,
535
+ "evidence_required": [
536
+ {
537
+ "field": "rendering_policy",
538
+ "description": "How hidden/blocked content is rendered.",
539
+ "upload_policy": "upload-with-consent"
540
+ }
541
+ ]
542
+ }
543
+ }
544
+ },
545
+ {
546
+ "id": "typescript.moderation.confirmation-ux-present",
547
+ "version": 1,
548
+ "title": "TypeScript destructive moderation actions must show confirmation",
549
+ "severity": "warning",
550
+ "rationale": "Report, block, and mute are destructive actions that require user confirmation to prevent accidental submissions.",
551
+ "applies_when": {
552
+ "platforms": [
553
+ "typescript"
554
+ ],
555
+ "outcomes": [
556
+ "add-moderation",
557
+ "validate-setup"
558
+ ]
559
+ },
560
+ "enforcement": {
561
+ "deterministic": [
562
+ {
563
+ "check": "validator-finding-absent",
564
+ "finding_rule_id": "typescript.moderation.confirmation-ux-present"
565
+ }
566
+ ],
567
+ "attestation": {
568
+ "allowed": true,
569
+ "host_agent_min_confidence": "high",
570
+ "human_allowed": true,
571
+ "evidence_required": [
572
+ {
573
+ "field": "confirmation_mechanism",
574
+ "description": "What confirmation UX is shown before destructive actions.",
575
+ "upload_policy": "upload-with-consent"
576
+ }
577
+ ]
578
+ }
579
+ }
580
+ },
581
+ {
582
+ "id": "react-native.moderation.confirmation-ux-present",
583
+ "version": 1,
584
+ "title": "React Native destructive moderation actions must show confirmation",
585
+ "severity": "warning",
586
+ "rationale": "Report, block, and mute are destructive actions that require user confirmation to prevent accidental submissions.",
587
+ "applies_when": {
588
+ "platforms": [
589
+ "react-native"
590
+ ],
591
+ "outcomes": [
592
+ "add-moderation",
593
+ "validate-setup"
594
+ ]
595
+ },
596
+ "enforcement": {
597
+ "deterministic": [
598
+ {
599
+ "check": "validator-finding-absent",
600
+ "finding_rule_id": "react-native.moderation.confirmation-ux-present"
601
+ }
602
+ ],
603
+ "attestation": {
604
+ "allowed": true,
605
+ "host_agent_min_confidence": "high",
606
+ "human_allowed": true,
607
+ "evidence_required": [
608
+ {
609
+ "field": "confirmation_mechanism",
610
+ "description": "What confirmation UX is shown before destructive actions.",
611
+ "upload_policy": "upload-with-consent"
612
+ }
613
+ ]
614
+ }
615
+ }
616
+ },
617
+ {
618
+ "id": "android.moderation.confirmation-ux-present",
619
+ "version": 1,
620
+ "title": "Android destructive moderation actions must show confirmation",
621
+ "severity": "warning",
622
+ "rationale": "Report, block, and mute are destructive actions that require user confirmation to prevent accidental submissions.",
623
+ "applies_when": {
624
+ "platforms": [
625
+ "android"
626
+ ],
627
+ "outcomes": [
628
+ "add-moderation",
629
+ "validate-setup"
630
+ ]
631
+ },
632
+ "enforcement": {
633
+ "deterministic": [
634
+ {
635
+ "check": "validator-finding-absent",
636
+ "finding_rule_id": "android.moderation.confirmation-ux-present"
637
+ }
638
+ ],
639
+ "attestation": {
640
+ "allowed": true,
641
+ "host_agent_min_confidence": "high",
642
+ "human_allowed": true,
643
+ "evidence_required": [
644
+ {
645
+ "field": "confirmation_mechanism",
646
+ "description": "What confirmation UX is shown before destructive actions.",
647
+ "upload_policy": "upload-with-consent"
648
+ }
649
+ ]
650
+ }
651
+ }
652
+ },
653
+ {
654
+ "id": "flutter.moderation.confirmation-ux-present",
655
+ "version": 1,
656
+ "title": "Flutter destructive moderation actions must show confirmation",
657
+ "severity": "warning",
658
+ "rationale": "Report, block, and mute are destructive actions that require user confirmation to prevent accidental submissions.",
659
+ "applies_when": {
660
+ "platforms": [
661
+ "flutter"
662
+ ],
663
+ "outcomes": [
664
+ "add-moderation",
665
+ "validate-setup"
666
+ ]
667
+ },
668
+ "enforcement": {
669
+ "deterministic": [
670
+ {
671
+ "check": "validator-finding-absent",
672
+ "finding_rule_id": "flutter.moderation.confirmation-ux-present"
673
+ }
674
+ ],
675
+ "attestation": {
676
+ "allowed": true,
677
+ "host_agent_min_confidence": "high",
678
+ "human_allowed": true,
679
+ "evidence_required": [
680
+ {
681
+ "field": "confirmation_mechanism",
682
+ "description": "What confirmation UX is shown before destructive actions.",
683
+ "upload_policy": "upload-with-consent"
684
+ }
685
+ ]
686
+ }
687
+ }
688
+ },
689
+ {
690
+ "id": "ios.moderation.confirmation-ux-present",
691
+ "version": 1,
692
+ "title": "iOS destructive moderation actions must show confirmation",
693
+ "severity": "warning",
694
+ "rationale": "Report, block, and mute are destructive actions that require user confirmation to prevent accidental submissions.",
695
+ "applies_when": {
696
+ "platforms": [
697
+ "ios"
698
+ ],
699
+ "outcomes": [
700
+ "add-moderation",
701
+ "validate-setup"
702
+ ]
703
+ },
704
+ "enforcement": {
705
+ "deterministic": [
706
+ {
707
+ "check": "validator-finding-absent",
708
+ "finding_rule_id": "ios.moderation.confirmation-ux-present"
709
+ }
710
+ ],
711
+ "attestation": {
712
+ "allowed": true,
713
+ "host_agent_min_confidence": "high",
714
+ "human_allowed": true,
715
+ "evidence_required": [
716
+ {
717
+ "field": "confirmation_mechanism",
718
+ "description": "What confirmation UX is shown before destructive actions.",
719
+ "upload_policy": "upload-with-consent"
720
+ }
721
+ ]
722
+ }
723
+ }
724
+ },
725
+ {
726
+ "id": "typescript.moderation.role-gated-action",
727
+ "version": 1,
728
+ "title": "TypeScript moderation actions must be role-gated",
729
+ "severity": "warning",
730
+ "rationale": "Moderation actions (flag, delete, ban, mute) 403 server-side for users without the moderator role. Gate the action or the button with currentUser.roles.includes('moderator') or an equivalent permission check before rendering.",
731
+ "applies_when": {
732
+ "platforms": [
733
+ "typescript"
734
+ ],
735
+ "outcomes": [
736
+ "add-moderation",
737
+ "validate-setup"
738
+ ]
739
+ },
740
+ "enforcement": {
741
+ "deterministic": [
742
+ {
743
+ "check": "validator-finding-absent",
744
+ "finding_rule_id": "typescript.moderation.role-gated-action"
745
+ }
746
+ ],
747
+ "attestation": {
748
+ "allowed": true,
749
+ "host_agent_min_confidence": "high",
750
+ "human_allowed": true,
751
+ "evidence_required": [
752
+ {
753
+ "field": "role_check",
754
+ "description": "How the component ensures the user is a moderator before rendering the action.",
755
+ "upload_policy": "upload-with-consent"
756
+ }
757
+ ]
758
+ }
759
+ }
760
+ },
761
+ {
762
+ "id": "react-native.moderation.role-gated-action",
763
+ "version": 1,
764
+ "title": "React Native moderation actions must be role-gated",
765
+ "severity": "warning",
766
+ "rationale": "Moderation actions (flag, delete, ban, mute) 403 server-side for users without the moderator role. Gate the Pressable/TouchableOpacity with currentUser.roles.includes('moderator') or a permission selector from your auth store before rendering.",
767
+ "applies_when": {
768
+ "platforms": [
769
+ "react-native"
770
+ ],
771
+ "outcomes": [
772
+ "add-moderation",
773
+ "validate-setup"
774
+ ]
775
+ },
776
+ "enforcement": {
777
+ "deterministic": [
778
+ {
779
+ "check": "validator-finding-absent",
780
+ "finding_rule_id": "react-native.moderation.role-gated-action"
781
+ }
782
+ ],
783
+ "attestation": {
784
+ "allowed": true,
785
+ "host_agent_min_confidence": "high",
786
+ "human_allowed": true,
787
+ "evidence_required": [
788
+ {
789
+ "field": "role_check",
790
+ "description": "How the component ensures the user is a moderator before rendering the action.",
791
+ "upload_policy": "upload-with-consent"
792
+ }
793
+ ]
794
+ }
795
+ }
796
+ },
797
+ {
798
+ "id": "android.moderation.role-gated-action",
799
+ "version": 1,
800
+ "title": "Android moderation actions must be role-gated",
801
+ "severity": "warning",
802
+ "rationale": "Moderation actions (flag, delete, ban, mute) throw on the server for users without the moderator role. Guard the action call site in Kotlin with currentUser.roles.contains(\"moderator\") or community.hasModeratorRole(currentUser.userId) before showing the button.",
803
+ "applies_when": {
804
+ "platforms": [
805
+ "android"
806
+ ],
807
+ "outcomes": [
808
+ "add-moderation",
809
+ "validate-setup"
810
+ ]
811
+ },
812
+ "enforcement": {
813
+ "deterministic": [
814
+ {
815
+ "check": "validator-finding-absent",
816
+ "finding_rule_id": "android.moderation.role-gated-action"
817
+ }
818
+ ],
819
+ "attestation": {
820
+ "allowed": true,
821
+ "host_agent_min_confidence": "high",
822
+ "human_allowed": true,
823
+ "evidence_required": [
824
+ {
825
+ "field": "role_check",
826
+ "description": "How the component ensures the user is a moderator before rendering the action.",
827
+ "upload_policy": "upload-with-consent"
828
+ }
829
+ ]
830
+ }
831
+ }
832
+ },
833
+ {
834
+ "id": "flutter.moderation.role-gated-action",
835
+ "version": 1,
836
+ "title": "Flutter moderation actions must be role-gated",
837
+ "severity": "warning",
838
+ "rationale": "Moderation actions (flag, delete, ban, mute) throw AmityException server-side for users without the moderator role. In Dart, wrap the IconButton or action handler with currentUser.roles.contains('moderator') or community.hasModeratorRole(currentUser.userId) before rendering.",
839
+ "applies_when": {
840
+ "platforms": [
841
+ "flutter"
842
+ ],
843
+ "outcomes": [
844
+ "add-moderation",
845
+ "validate-setup"
846
+ ]
847
+ },
848
+ "enforcement": {
849
+ "deterministic": [
850
+ {
851
+ "check": "validator-finding-absent",
852
+ "finding_rule_id": "flutter.moderation.role-gated-action"
853
+ }
854
+ ],
855
+ "attestation": {
856
+ "allowed": true,
857
+ "host_agent_min_confidence": "high",
858
+ "human_allowed": true,
859
+ "evidence_required": [
860
+ {
861
+ "field": "role_check",
862
+ "description": "How the component ensures the user is a moderator before rendering the action.",
863
+ "upload_policy": "upload-with-consent"
864
+ }
865
+ ]
866
+ }
867
+ }
868
+ },
869
+ {
870
+ "id": "ios.moderation.role-gated-action",
871
+ "version": 1,
872
+ "title": "iOS moderation actions must be role-gated",
873
+ "severity": "warning",
874
+ "rationale": "Moderation actions (flag, delete, ban, mute) return AmityError.forbidden for users without the moderator role. In Swift, guard the SwiftUI Button or UIKit handler with currentUser.roles?.contains(\"moderator\") == true or community.hasModeratorRole(userId:) before rendering.",
875
+ "applies_when": {
876
+ "platforms": [
877
+ "ios"
878
+ ],
879
+ "outcomes": [
880
+ "add-moderation",
881
+ "validate-setup"
882
+ ]
883
+ },
884
+ "enforcement": {
885
+ "deterministic": [
886
+ {
887
+ "check": "validator-finding-absent",
888
+ "finding_rule_id": "ios.moderation.role-gated-action"
889
+ }
890
+ ],
891
+ "attestation": {
892
+ "allowed": true,
893
+ "host_agent_min_confidence": "high",
894
+ "human_allowed": true,
895
+ "evidence_required": [
896
+ {
897
+ "field": "role_check",
898
+ "description": "How the component ensures the user is a moderator before rendering the action.",
899
+ "upload_policy": "upload-with-consent"
900
+ }
901
+ ]
902
+ }
903
+ }
904
+ },
905
+ {
906
+ "id": "typescript.flag-count.not-leaked-to-non-mods",
907
+ "version": 1,
908
+ "title": "TypeScript flagCount must not leak to non-moderators",
909
+ "severity": "warning",
910
+ "rationale": "post.flagCount and comment.flagCount are moderator-only fields. Rendering them via JSX (e.g. {post.flagCount}) without a role gate leaks moderation signal to everyone, including the content author. Wrap the JSX expression with isModerator && {...} or hoist into a moderator-only component.",
911
+ "applies_when": {
912
+ "platforms": [
913
+ "typescript"
914
+ ],
915
+ "outcomes": [
916
+ "add-feed",
917
+ "add-moderation",
918
+ "validate-setup"
919
+ ]
920
+ },
921
+ "enforcement": {
922
+ "deterministic": [
923
+ {
924
+ "check": "validator-finding-absent",
925
+ "finding_rule_id": "typescript.flag-count.not-leaked-to-non-mods"
926
+ }
927
+ ],
928
+ "attestation": {
929
+ "allowed": true,
930
+ "host_agent_min_confidence": "high",
931
+ "human_allowed": true,
932
+ "evidence_required": [
933
+ {
934
+ "field": "flag_count_visibility",
935
+ "description": "How the component ensures flagCount is only visible to moderators.",
936
+ "upload_policy": "upload-with-consent"
937
+ }
938
+ ]
939
+ }
940
+ }
941
+ },
942
+ {
943
+ "id": "react-native.flag-count.not-leaked-to-non-mods",
944
+ "version": 1,
945
+ "title": "React Native flagCount must not leak to non-moderators",
946
+ "severity": "warning",
947
+ "rationale": "post.flagCount and comment.flagCount are moderator-only fields. Rendering them inside a <Text>{post.flagCount}</Text> without a role gate exposes moderation signal to all users, including the content author. Gate the render with {isModerator && <Text>...</Text>} or a moderator-only screen.",
948
+ "applies_when": {
949
+ "platforms": [
950
+ "react-native"
951
+ ],
952
+ "outcomes": [
953
+ "add-feed",
954
+ "add-moderation",
955
+ "validate-setup"
956
+ ]
957
+ },
958
+ "enforcement": {
959
+ "deterministic": [
960
+ {
961
+ "check": "validator-finding-absent",
962
+ "finding_rule_id": "react-native.flag-count.not-leaked-to-non-mods"
963
+ }
964
+ ],
965
+ "attestation": {
966
+ "allowed": true,
967
+ "host_agent_min_confidence": "high",
968
+ "human_allowed": true,
969
+ "evidence_required": [
970
+ {
971
+ "field": "flag_count_visibility",
972
+ "description": "How the component ensures flagCount is only visible to moderators.",
973
+ "upload_policy": "upload-with-consent"
974
+ }
975
+ ]
976
+ }
977
+ }
978
+ },
979
+ {
980
+ "id": "android.flag-count.not-leaked-to-non-mods",
981
+ "version": 1,
982
+ "title": "Android flagCount must not leak to non-moderators",
983
+ "severity": "warning",
984
+ "rationale": "post.flagCount and comment.flagCount are moderator-only fields. Binding them to a Compose Text() or RecyclerView ViewHolder without a role gate exposes moderation signal to every user, including the content author. Gate with if (isModerator) Text(\"$flagCount flags\") or wrap the ViewHolder in a moderator-only adapter.",
985
+ "applies_when": {
986
+ "platforms": [
987
+ "android"
988
+ ],
989
+ "outcomes": [
990
+ "add-feed",
991
+ "add-moderation",
992
+ "validate-setup"
993
+ ]
994
+ },
995
+ "enforcement": {
996
+ "deterministic": [
997
+ {
998
+ "check": "validator-finding-absent",
999
+ "finding_rule_id": "android.flag-count.not-leaked-to-non-mods"
1000
+ }
1001
+ ],
1002
+ "attestation": {
1003
+ "allowed": true,
1004
+ "host_agent_min_confidence": "high",
1005
+ "human_allowed": true,
1006
+ "evidence_required": [
1007
+ {
1008
+ "field": "flag_count_visibility",
1009
+ "description": "How the component ensures flagCount is only visible to moderators.",
1010
+ "upload_policy": "upload-with-consent"
1011
+ }
1012
+ ]
1013
+ }
1014
+ }
1015
+ },
1016
+ {
1017
+ "id": "flutter.flag-count.not-leaked-to-non-mods",
1018
+ "version": 1,
1019
+ "title": "Flutter flagCount must not leak to non-moderators",
1020
+ "severity": "warning",
1021
+ "rationale": "post.flagCount and comment.flagCount are moderator-only fields. Rendering them in a Text(post.flagCount.toString()) widget without a role gate exposes moderation signal to every user, including the content author. Gate with if (isModerator) Text(...) or a Visibility widget controlled by the role check.",
1022
+ "applies_when": {
1023
+ "platforms": [
1024
+ "flutter"
1025
+ ],
1026
+ "outcomes": [
1027
+ "add-feed",
1028
+ "add-moderation",
1029
+ "validate-setup"
1030
+ ]
1031
+ },
1032
+ "enforcement": {
1033
+ "deterministic": [
1034
+ {
1035
+ "check": "validator-finding-absent",
1036
+ "finding_rule_id": "flutter.flag-count.not-leaked-to-non-mods"
1037
+ }
1038
+ ],
1039
+ "attestation": {
1040
+ "allowed": true,
1041
+ "host_agent_min_confidence": "high",
1042
+ "human_allowed": true,
1043
+ "evidence_required": [
1044
+ {
1045
+ "field": "flag_count_visibility",
1046
+ "description": "How the component ensures flagCount is only visible to moderators.",
1047
+ "upload_policy": "upload-with-consent"
1048
+ }
1049
+ ]
1050
+ }
1051
+ }
1052
+ },
1053
+ {
1054
+ "id": "ios.flag-count.not-leaked-to-non-mods",
1055
+ "version": 1,
1056
+ "title": "iOS flagCount must not leak to non-moderators",
1057
+ "severity": "warning",
1058
+ "rationale": "post.flagCount and comment.flagCount are moderator-only fields. Rendering them in a SwiftUI Text(\"\\(post.flagCount)\") or UILabel.text without a role gate exposes moderation signal to every user, including the content author. Gate with if isModerator { Text(...) } in SwiftUI or set isHidden in UIKit based on the role check.",
1059
+ "applies_when": {
1060
+ "platforms": [
1061
+ "ios"
1062
+ ],
1063
+ "outcomes": [
1064
+ "add-feed",
1065
+ "add-moderation",
1066
+ "validate-setup"
1067
+ ]
1068
+ },
1069
+ "enforcement": {
1070
+ "deterministic": [
1071
+ {
1072
+ "check": "validator-finding-absent",
1073
+ "finding_rule_id": "ios.flag-count.not-leaked-to-non-mods"
1074
+ }
1075
+ ],
1076
+ "attestation": {
1077
+ "allowed": true,
1078
+ "host_agent_min_confidence": "high",
1079
+ "human_allowed": true,
1080
+ "evidence_required": [
1081
+ {
1082
+ "field": "flag_count_visibility",
1083
+ "description": "How the component ensures flagCount is only visible to moderators.",
1084
+ "upload_policy": "upload-with-consent"
1085
+ }
1086
+ ]
1087
+ }
1088
+ }
1089
+ },
1090
+ {
1091
+ "id": "typescript.user.ban-state-respected",
1092
+ "version": 1,
1093
+ "title": "TypeScript ban state must be respected",
1094
+ "severity": "warning",
1095
+ "rationale": "Banned users see interaction buttons that 403 server-side, surfacing as confusing toasts. In JSX, gate composer buttons with !currentUser.isGlobalBan && <Button .../> or render a 'You are banned' state instead. Channel-muted state requires the analogous !channel.isMuted check.",
1096
+ "applies_when": {
1097
+ "platforms": [
1098
+ "typescript"
1099
+ ],
1100
+ "outcomes": [
1101
+ "add-feed",
1102
+ "add-chat",
1103
+ "add-comments",
1104
+ "add-moderation",
1105
+ "validate-setup"
1106
+ ]
1107
+ },
1108
+ "enforcement": {
1109
+ "deterministic": [
1110
+ {
1111
+ "check": "validator-finding-absent",
1112
+ "finding_rule_id": "typescript.user.ban-state-respected"
1113
+ }
1114
+ ],
1115
+ "attestation": {
1116
+ "allowed": true,
1117
+ "host_agent_min_confidence": "high",
1118
+ "human_allowed": true,
1119
+ "evidence_required": [
1120
+ {
1121
+ "field": "ban_state_handling",
1122
+ "description": "How the component ensures the user is not banned before rendering the action.",
1123
+ "upload_policy": "upload-with-consent"
1124
+ }
1125
+ ]
1126
+ }
1127
+ }
1128
+ },
1129
+ {
1130
+ "id": "react-native.user.ban-state-respected",
1131
+ "version": 1,
1132
+ "title": "React Native ban state must be respected",
1133
+ "severity": "warning",
1134
+ "rationale": "Banned users see Pressable/TouchableOpacity composers that 403 server-side, surfacing as confusing toasts on mobile. Gate the composer with !currentUser.isGlobalBan && <Pressable .../>, or use a 'You are banned' empty state component. For chat, also gate with !channel.isMuted and !member.isMuted.",
1135
+ "applies_when": {
1136
+ "platforms": [
1137
+ "react-native"
1138
+ ],
1139
+ "outcomes": [
1140
+ "add-feed",
1141
+ "add-chat",
1142
+ "add-comments",
1143
+ "add-moderation",
1144
+ "validate-setup"
1145
+ ]
1146
+ },
1147
+ "enforcement": {
1148
+ "deterministic": [
1149
+ {
1150
+ "check": "validator-finding-absent",
1151
+ "finding_rule_id": "react-native.user.ban-state-respected"
1152
+ }
1153
+ ],
1154
+ "attestation": {
1155
+ "allowed": true,
1156
+ "host_agent_min_confidence": "high",
1157
+ "human_allowed": true,
1158
+ "evidence_required": [
1159
+ {
1160
+ "field": "ban_state_handling",
1161
+ "description": "How the component ensures the user is not banned before rendering the action.",
1162
+ "upload_policy": "upload-with-consent"
1163
+ }
1164
+ ]
1165
+ }
1166
+ }
1167
+ },
1168
+ {
1169
+ "id": "android.user.ban-state-respected",
1170
+ "version": 1,
1171
+ "title": "Android ban state must be respected",
1172
+ "severity": "warning",
1173
+ "rationale": "Banned users see composer Buttons or FABs that throw AmityException server-side, surfacing as a Toast. In Kotlin/Compose, gate with if (!currentUser.isGlobalBan) Button(...) or set View.GONE based on ViewModel state. For chat, also check !channel.isMuted and !member.isMuted.",
1174
+ "applies_when": {
1175
+ "platforms": [
1176
+ "android"
1177
+ ],
1178
+ "outcomes": [
1179
+ "add-feed",
1180
+ "add-chat",
1181
+ "add-comments",
1182
+ "add-moderation",
1183
+ "validate-setup"
1184
+ ]
1185
+ },
1186
+ "enforcement": {
1187
+ "deterministic": [
1188
+ {
1189
+ "check": "validator-finding-absent",
1190
+ "finding_rule_id": "android.user.ban-state-respected"
1191
+ }
1192
+ ],
1193
+ "attestation": {
1194
+ "allowed": true,
1195
+ "host_agent_min_confidence": "high",
1196
+ "human_allowed": true,
1197
+ "evidence_required": [
1198
+ {
1199
+ "field": "ban_state_handling",
1200
+ "description": "How the component ensures the user is not banned before rendering the action.",
1201
+ "upload_policy": "upload-with-consent"
1202
+ }
1203
+ ]
1204
+ }
1205
+ }
1206
+ },
1207
+ {
1208
+ "id": "flutter.user.ban-state-respected",
1209
+ "version": 1,
1210
+ "title": "Flutter ban state must be respected",
1211
+ "severity": "warning",
1212
+ "rationale": "Banned users see composer ElevatedButton or FloatingActionButton widgets that throw AmityException server-side, surfacing as a SnackBar. In Dart, gate with if (!currentUser.isGlobalBan) ElevatedButton(...) or use a Visibility widget tied to the ban state. For chat, also check !channel.isMuted and !member.isMuted.",
1213
+ "applies_when": {
1214
+ "platforms": [
1215
+ "flutter"
1216
+ ],
1217
+ "outcomes": [
1218
+ "add-feed",
1219
+ "add-chat",
1220
+ "add-comments",
1221
+ "add-moderation",
1222
+ "validate-setup"
1223
+ ]
1224
+ },
1225
+ "enforcement": {
1226
+ "deterministic": [
1227
+ {
1228
+ "check": "validator-finding-absent",
1229
+ "finding_rule_id": "flutter.user.ban-state-respected"
1230
+ }
1231
+ ],
1232
+ "attestation": {
1233
+ "allowed": true,
1234
+ "host_agent_min_confidence": "high",
1235
+ "human_allowed": true,
1236
+ "evidence_required": [
1237
+ {
1238
+ "field": "ban_state_handling",
1239
+ "description": "How the component ensures the user is not banned before rendering the action.",
1240
+ "upload_policy": "upload-with-consent"
1241
+ }
1242
+ ]
1243
+ }
1244
+ }
1245
+ },
1246
+ {
1247
+ "id": "ios.user.ban-state-respected",
1248
+ "version": 1,
1249
+ "title": "iOS ban state must be respected",
1250
+ "severity": "warning",
1251
+ "rationale": "Banned users see composer Buttons or UIBarButtonItems that return AmityError.forbidden server-side, surfacing as an alert. In Swift, guard with if !currentUser.isGlobalBan { Button(...) } in SwiftUI or set isEnabled = false in UIKit based on ban state. For chat, also check !channel.isMuted and !member.isMuted.",
1252
+ "applies_when": {
1253
+ "platforms": [
1254
+ "ios"
1255
+ ],
1256
+ "outcomes": [
1257
+ "add-feed",
1258
+ "add-chat",
1259
+ "add-comments",
1260
+ "add-moderation",
1261
+ "validate-setup"
1262
+ ]
1263
+ },
1264
+ "enforcement": {
1265
+ "deterministic": [
1266
+ {
1267
+ "check": "validator-finding-absent",
1268
+ "finding_rule_id": "ios.user.ban-state-respected"
1269
+ }
1270
+ ],
1271
+ "attestation": {
1272
+ "allowed": true,
1273
+ "host_agent_min_confidence": "high",
1274
+ "human_allowed": true,
1275
+ "evidence_required": [
1276
+ {
1277
+ "field": "ban_state_handling",
1278
+ "description": "How the component ensures the user is not banned before rendering the action.",
1279
+ "upload_policy": "upload-with-consent"
1280
+ }
1281
+ ]
1282
+ }
1283
+ }
1284
+ }
1285
+ ]
1286
+ }