@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,66 @@
1
+ {
2
+ "domain": "network",
3
+ "schema_version": 1,
4
+ "rules": [
5
+ {
6
+ "id": "android.network.error-handling-present",
7
+ "version": 1,
8
+ "title": "Android SDK calls must have error handling",
9
+ "severity": "warning",
10
+ "rationale": "SDK calls fail in real networks. Unhandled login/create/fetch errors produce broken UX or unhandled exception crashes.",
11
+ "applies_when": { "platforms": ["android"], "outcomes": ["validate-setup"] },
12
+ "enforcement": {
13
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "android.network.error-handling-present" }],
14
+ "attestation": { "allowed": true, "host_agent_min_confidence": "medium", "human_allowed": true, "evidence_required": [{ "field": "error_handling_flow", "description": "Code showing error handling around SDK calls (try/catch, runCatching, coroutine exception handler).", "upload_policy": "upload-with-consent" }] }
15
+ }
16
+ },
17
+ {
18
+ "id": "flutter.network.error-handling-present",
19
+ "version": 1,
20
+ "title": "Flutter SDK calls must have error handling",
21
+ "severity": "warning",
22
+ "rationale": "SDK calls fail in real networks. Unhandled login/create/fetch errors produce broken UX or unhandled exception crashes.",
23
+ "applies_when": { "platforms": ["flutter"], "outcomes": ["validate-setup"] },
24
+ "enforcement": {
25
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "flutter.network.error-handling-present" }],
26
+ "attestation": { "allowed": true, "host_agent_min_confidence": "medium", "human_allowed": true, "evidence_required": [{ "field": "error_handling_flow", "description": "Code showing error handling around SDK calls (try/catch, .catchError).", "upload_policy": "upload-with-consent" }] }
27
+ }
28
+ },
29
+ {
30
+ "id": "typescript.network.error-handling-present",
31
+ "version": 1,
32
+ "title": "TypeScript SDK calls must have error handling",
33
+ "severity": "warning",
34
+ "rationale": "SDK calls fail in real networks. Unhandled login/create/fetch errors produce broken UX or unhandled promise rejections.",
35
+ "applies_when": { "platforms": ["typescript"], "outcomes": ["validate-setup"] },
36
+ "enforcement": {
37
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "typescript.network.error-handling-present" }],
38
+ "attestation": { "allowed": true, "host_agent_min_confidence": "medium", "human_allowed": true, "evidence_required": [{ "field": "error_handling_flow", "description": "Code showing error handling around SDK calls (try/catch, .catch()).", "upload_policy": "upload-with-consent" }] }
39
+ }
40
+ },
41
+ {
42
+ "id": "react-native.network.error-handling-present",
43
+ "version": 1,
44
+ "title": "React Native SDK calls must have error handling",
45
+ "severity": "warning",
46
+ "rationale": "SDK calls fail in real networks. Unhandled login/create/fetch errors produce broken UX or unhandled promise rejections.",
47
+ "applies_when": { "platforms": ["react-native"], "outcomes": ["validate-setup"] },
48
+ "enforcement": {
49
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "react-native.network.error-handling-present" }],
50
+ "attestation": { "allowed": true, "host_agent_min_confidence": "medium", "human_allowed": true, "evidence_required": [{ "field": "error_handling_flow", "description": "Code showing error handling around SDK calls (try/catch, .catch()).", "upload_policy": "upload-with-consent" }] }
51
+ }
52
+ },
53
+ {
54
+ "id": "ios.network.error-handling-present",
55
+ "version": 1,
56
+ "title": "iOS SDK calls must have error handling",
57
+ "severity": "warning",
58
+ "rationale": "SDK calls fail in real networks. Unhandled login/create/fetch errors produce broken UX or unhandled crashes.",
59
+ "applies_when": { "platforms": ["ios"], "outcomes": ["validate-setup"] },
60
+ "enforcement": {
61
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "ios.network.error-handling-present" }],
62
+ "attestation": { "allowed": true, "host_agent_min_confidence": "medium", "human_allowed": true, "evidence_required": [{ "field": "error_handling_flow", "description": "Code showing error handling around SDK calls (do/catch, try?, Result).", "upload_policy": "upload-with-consent" }] }
63
+ }
64
+ }
65
+ ]
66
+ }
package/rules/push.yaml CHANGED
@@ -8,10 +8,34 @@
8
8
  "title": "Android push unregister must exist on logout or user switch",
9
9
  "severity": "warning",
10
10
  "rationale": "Registered device tokens are associated with a user. Missing unregister can deliver notifications to the wrong user.",
11
- "applies_when": { "platforms": ["android"], "outcomes": ["setup-push", "validate-setup"] },
11
+ "applies_when": {
12
+ "platforms": [
13
+ "android"
14
+ ],
15
+ "outcomes": [
16
+ "setup-push",
17
+ "validate-setup"
18
+ ]
19
+ },
12
20
  "enforcement": {
13
- "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "android.push.unregister.present" }],
14
- "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "trigger", "description": "Logout or user-switch flow that unregisters the device token.", "upload_policy": "upload-with-consent" }] }
21
+ "deterministic": [
22
+ {
23
+ "check": "validator-finding-absent",
24
+ "finding_rule_id": "android.push.unregister.present"
25
+ }
26
+ ],
27
+ "attestation": {
28
+ "allowed": true,
29
+ "host_agent_min_confidence": "high",
30
+ "human_allowed": true,
31
+ "evidence_required": [
32
+ {
33
+ "field": "trigger",
34
+ "description": "Logout or user-switch flow that unregisters the device token.",
35
+ "upload_policy": "upload-with-consent"
36
+ }
37
+ ]
38
+ }
15
39
  }
16
40
  },
17
41
  {
@@ -20,10 +44,34 @@
20
44
  "title": "Flutter push unregister must exist on logout or user switch",
21
45
  "severity": "warning",
22
46
  "rationale": "Registered device tokens are associated with a user. Missing unregister can deliver notifications to the wrong user.",
23
- "applies_when": { "platforms": ["flutter"], "outcomes": ["setup-push", "validate-setup"] },
47
+ "applies_when": {
48
+ "platforms": [
49
+ "flutter"
50
+ ],
51
+ "outcomes": [
52
+ "setup-push",
53
+ "validate-setup"
54
+ ]
55
+ },
24
56
  "enforcement": {
25
- "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "flutter.push.unregister.present" }],
26
- "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "trigger", "description": "Logout or user-switch flow that unregisters the device token.", "upload_policy": "upload-with-consent" }] }
57
+ "deterministic": [
58
+ {
59
+ "check": "validator-finding-absent",
60
+ "finding_rule_id": "flutter.push.unregister.present"
61
+ }
62
+ ],
63
+ "attestation": {
64
+ "allowed": true,
65
+ "host_agent_min_confidence": "high",
66
+ "human_allowed": true,
67
+ "evidence_required": [
68
+ {
69
+ "field": "trigger",
70
+ "description": "Logout or user-switch flow that unregisters the device token.",
71
+ "upload_policy": "upload-with-consent"
72
+ }
73
+ ]
74
+ }
27
75
  }
28
76
  },
29
77
  {
@@ -32,10 +80,34 @@
32
80
  "title": "TypeScript push unregister must exist on logout or user switch",
33
81
  "severity": "warning",
34
82
  "rationale": "Registered device tokens are associated with a user. Missing unregister can deliver notifications to the wrong user.",
35
- "applies_when": { "platforms": ["typescript"], "outcomes": ["setup-push", "validate-setup"] },
83
+ "applies_when": {
84
+ "platforms": [
85
+ "typescript"
86
+ ],
87
+ "outcomes": [
88
+ "setup-push",
89
+ "validate-setup"
90
+ ]
91
+ },
36
92
  "enforcement": {
37
- "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "typescript.push.unregister.present" }],
38
- "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "trigger", "description": "Logout or user-switch flow that unregisters the device token.", "upload_policy": "upload-with-consent" }] }
93
+ "deterministic": [
94
+ {
95
+ "check": "validator-finding-absent",
96
+ "finding_rule_id": "typescript.push.unregister.present"
97
+ }
98
+ ],
99
+ "attestation": {
100
+ "allowed": true,
101
+ "host_agent_min_confidence": "high",
102
+ "human_allowed": true,
103
+ "evidence_required": [
104
+ {
105
+ "field": "trigger",
106
+ "description": "Logout or user-switch flow that unregisters the device token.",
107
+ "upload_policy": "upload-with-consent"
108
+ }
109
+ ]
110
+ }
39
111
  }
40
112
  },
41
113
  {
@@ -44,10 +116,34 @@
44
116
  "title": "React Native push unregister must exist on logout or user switch",
45
117
  "severity": "warning",
46
118
  "rationale": "Registered device tokens are associated with a user. Missing unregister can deliver notifications to the wrong user.",
47
- "applies_when": { "platforms": ["react-native"], "outcomes": ["setup-push", "validate-setup"] },
119
+ "applies_when": {
120
+ "platforms": [
121
+ "react-native"
122
+ ],
123
+ "outcomes": [
124
+ "setup-push",
125
+ "validate-setup"
126
+ ]
127
+ },
48
128
  "enforcement": {
49
- "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "react-native.push.unregister.present" }],
50
- "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "trigger", "description": "Logout or user-switch flow that unregisters the device token.", "upload_policy": "upload-with-consent" }] }
129
+ "deterministic": [
130
+ {
131
+ "check": "validator-finding-absent",
132
+ "finding_rule_id": "react-native.push.unregister.present"
133
+ }
134
+ ],
135
+ "attestation": {
136
+ "allowed": true,
137
+ "host_agent_min_confidence": "high",
138
+ "human_allowed": true,
139
+ "evidence_required": [
140
+ {
141
+ "field": "trigger",
142
+ "description": "Logout or user-switch flow that unregisters the device token.",
143
+ "upload_policy": "upload-with-consent"
144
+ }
145
+ ]
146
+ }
51
147
  }
52
148
  },
53
149
  {
@@ -56,10 +152,34 @@
56
152
  "title": "iOS push unregister must exist on logout or user switch",
57
153
  "severity": "warning",
58
154
  "rationale": "Registered device tokens are associated with a user. Missing unregister can deliver notifications to the wrong user.",
59
- "applies_when": { "platforms": ["ios"], "outcomes": ["setup-push", "validate-setup"] },
155
+ "applies_when": {
156
+ "platforms": [
157
+ "ios"
158
+ ],
159
+ "outcomes": [
160
+ "setup-push",
161
+ "validate-setup"
162
+ ]
163
+ },
60
164
  "enforcement": {
61
- "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "ios.push.unregister.present" }],
62
- "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "trigger", "description": "Logout or user-switch flow that unregisters the device token.", "upload_policy": "upload-with-consent" }] }
165
+ "deterministic": [
166
+ {
167
+ "check": "validator-finding-absent",
168
+ "finding_rule_id": "ios.push.unregister.present"
169
+ }
170
+ ],
171
+ "attestation": {
172
+ "allowed": true,
173
+ "host_agent_min_confidence": "high",
174
+ "human_allowed": true,
175
+ "evidence_required": [
176
+ {
177
+ "field": "trigger",
178
+ "description": "Logout or user-switch flow that unregisters the device token.",
179
+ "upload_policy": "upload-with-consent"
180
+ }
181
+ ]
182
+ }
63
183
  }
64
184
  },
65
185
  {
@@ -68,7 +188,14 @@
68
188
  "title": "Firebase configuration file must be present for Android push setup",
69
189
  "severity": "warning",
70
190
  "rationale": "Firebase Cloud Messaging requires google-services.json in the Android app module to obtain device tokens. Without it, the SDK cannot register for push notifications.",
71
- "applies_when": { "platforms": ["android"], "outcomes": ["setup-push"] },
191
+ "applies_when": {
192
+ "platforms": [
193
+ "android"
194
+ ],
195
+ "outcomes": [
196
+ "setup-push"
197
+ ]
198
+ },
72
199
  "enforcement": {
73
200
  "blockers": [
74
201
  {
@@ -90,6 +217,330 @@
90
217
  ]
91
218
  }
92
219
  }
220
+ },
221
+ {
222
+ "id": "ios.push.permission-prompt-before-register",
223
+ "version": 1,
224
+ "title": "iOS must request push permission before registering for remote notifications",
225
+ "severity": "warning",
226
+ "rationale": "Calling registerForRemoteNotifications without first requesting UNUserNotificationCenter.requestAuthorization means the OS will silently deny push delivery on iOS 10+.",
227
+ "applies_when": {
228
+ "platforms": [
229
+ "ios"
230
+ ],
231
+ "outcomes": [
232
+ "setup-push",
233
+ "validate-setup"
234
+ ]
235
+ },
236
+ "enforcement": {
237
+ "deterministic": [
238
+ {
239
+ "check": "validator-finding-absent",
240
+ "finding_rule_id": "ios.push.permission-prompt-before-register"
241
+ }
242
+ ],
243
+ "attestation": {
244
+ "allowed": true,
245
+ "host_agent_min_confidence": "high",
246
+ "human_allowed": true,
247
+ "evidence_required": [
248
+ {
249
+ "field": "trigger",
250
+ "description": "Code path showing UNUserNotificationCenter.requestAuthorization before push registration.",
251
+ "upload_policy": "upload-with-consent"
252
+ }
253
+ ]
254
+ }
255
+ }
256
+ },
257
+ {
258
+ "id": "android.push.payload-contract-respected",
259
+ "version": 1,
260
+ "title": "Android push payload must be forwarded to social.plus SDK handler",
261
+ "severity": "warning",
262
+ "rationale": "If onMessageReceived handles FCM payloads but does not forward social.plus messages to the SDK push handler, social notifications (new messages, reactions, mentions) will be silently dropped.",
263
+ "applies_when": {
264
+ "platforms": [
265
+ "android"
266
+ ],
267
+ "outcomes": [
268
+ "setup-push",
269
+ "validate-setup"
270
+ ]
271
+ },
272
+ "enforcement": {
273
+ "deterministic": [
274
+ {
275
+ "check": "validator-finding-absent",
276
+ "finding_rule_id": "android.push.payload-contract-respected"
277
+ }
278
+ ],
279
+ "attestation": {
280
+ "allowed": true,
281
+ "host_agent_min_confidence": "high",
282
+ "human_allowed": true,
283
+ "evidence_required": [
284
+ {
285
+ "field": "trigger",
286
+ "description": "Code forwarding social.plus push payloads to the SDK handler (e.g. AmityPush.handleNotification).",
287
+ "upload_policy": "upload-with-consent"
288
+ }
289
+ ]
290
+ }
291
+ }
292
+ },
293
+ {
294
+ "id": "flutter.push.payload-contract-respected",
295
+ "version": 1,
296
+ "title": "Flutter push payload must be forwarded to social.plus SDK handler",
297
+ "severity": "warning",
298
+ "rationale": "If FirebaseMessaging listeners handle payloads but do not forward social.plus messages to the SDK push handler, social notifications will be silently dropped.",
299
+ "applies_when": {
300
+ "platforms": [
301
+ "flutter"
302
+ ],
303
+ "outcomes": [
304
+ "setup-push",
305
+ "validate-setup"
306
+ ]
307
+ },
308
+ "enforcement": {
309
+ "deterministic": [
310
+ {
311
+ "check": "validator-finding-absent",
312
+ "finding_rule_id": "flutter.push.payload-contract-respected"
313
+ }
314
+ ],
315
+ "attestation": {
316
+ "allowed": true,
317
+ "host_agent_min_confidence": "high",
318
+ "human_allowed": true,
319
+ "evidence_required": [
320
+ {
321
+ "field": "trigger",
322
+ "description": "Code forwarding social.plus push payloads to the SDK handler (e.g. AmityPush.handleNotification).",
323
+ "upload_policy": "upload-with-consent"
324
+ }
325
+ ]
326
+ }
327
+ }
328
+ },
329
+ {
330
+ "id": "ios.push.payload-contract-respected",
331
+ "version": 1,
332
+ "title": "iOS push payload must be forwarded to social.plus SDK handler",
333
+ "severity": "warning",
334
+ "rationale": "If didReceiveRemoteNotification or UNUserNotificationCenter delegate handles payloads but does not forward social.plus messages to the SDK push handler, social notifications will be silently dropped.",
335
+ "applies_when": {
336
+ "platforms": [
337
+ "ios"
338
+ ],
339
+ "outcomes": [
340
+ "setup-push",
341
+ "validate-setup"
342
+ ]
343
+ },
344
+ "enforcement": {
345
+ "deterministic": [
346
+ {
347
+ "check": "validator-finding-absent",
348
+ "finding_rule_id": "ios.push.payload-contract-respected"
349
+ }
350
+ ],
351
+ "attestation": {
352
+ "allowed": true,
353
+ "host_agent_min_confidence": "high",
354
+ "human_allowed": true,
355
+ "evidence_required": [
356
+ {
357
+ "field": "trigger",
358
+ "description": "Code forwarding social.plus push payloads to the SDK handler (e.g. AmityPush.handleNotification).",
359
+ "upload_policy": "upload-with-consent"
360
+ }
361
+ ]
362
+ }
363
+ }
364
+ },
365
+ {
366
+ "id": "typescript.notifications.amity-preferences-configured",
367
+ "version": 1,
368
+ "title": "TypeScript Amity notification preferences must be configured",
369
+ "severity": "warning",
370
+ "rationale": "Amity exposes server-side notification preferences (mute community/user, per-event toggles) separate from web push. After registerPushNotification, call AmityCoreClient.notifications().getSettings() — typically inside a useEffect after login — so the running session respects the user's mutes instead of firing every event.",
371
+ "applies_when": {
372
+ "platforms": [
373
+ "typescript"
374
+ ],
375
+ "outcomes": [
376
+ "setup-push",
377
+ "validate-setup"
378
+ ]
379
+ },
380
+ "enforcement": {
381
+ "deterministic": [
382
+ {
383
+ "check": "validator-finding-absent",
384
+ "finding_rule_id": "typescript.notifications.amity-preferences-configured"
385
+ }
386
+ ],
387
+ "attestation": {
388
+ "allowed": true,
389
+ "host_agent_min_confidence": "high",
390
+ "human_allowed": true,
391
+ "evidence_required": [
392
+ {
393
+ "field": "preference_sync",
394
+ "description": "How the component ensures Amity server-side notification preferences are fetched or respected.",
395
+ "upload_policy": "upload-with-consent"
396
+ }
397
+ ]
398
+ }
399
+ }
400
+ },
401
+ {
402
+ "id": "react-native.notifications.amity-preferences-configured",
403
+ "version": 1,
404
+ "title": "React Native Amity notification preferences must be configured",
405
+ "severity": "warning",
406
+ "rationale": "Amity exposes server-side notification preferences (mute community/user, per-event toggles) separate from FCM/APNS device push. After registerPushNotification, call AmityCoreClient.notifications().getSettings() in your push setup hook so the JS bridge syncs the user's mutes before the first event fires.",
407
+ "applies_when": {
408
+ "platforms": [
409
+ "react-native"
410
+ ],
411
+ "outcomes": [
412
+ "setup-push",
413
+ "validate-setup"
414
+ ]
415
+ },
416
+ "enforcement": {
417
+ "deterministic": [
418
+ {
419
+ "check": "validator-finding-absent",
420
+ "finding_rule_id": "react-native.notifications.amity-preferences-configured"
421
+ }
422
+ ],
423
+ "attestation": {
424
+ "allowed": true,
425
+ "host_agent_min_confidence": "high",
426
+ "human_allowed": true,
427
+ "evidence_required": [
428
+ {
429
+ "field": "preference_sync",
430
+ "description": "How the component ensures Amity server-side notification preferences are fetched or respected.",
431
+ "upload_policy": "upload-with-consent"
432
+ }
433
+ ]
434
+ }
435
+ }
436
+ },
437
+ {
438
+ "id": "android.notifications.amity-preferences-configured",
439
+ "version": 1,
440
+ "title": "Android Amity notification preferences must be configured",
441
+ "severity": "warning",
442
+ "rationale": "Amity exposes server-side notification preferences (mute community/user, per-event toggles) separate from FCM. After AmityCoreClient.registerPushNotification(token).submit(), call AmityCoreClient.notifications().getSettings().submit() — typically in the same FirebaseMessagingService.onNewToken handler — so the session respects the user's mutes.",
443
+ "applies_when": {
444
+ "platforms": [
445
+ "android"
446
+ ],
447
+ "outcomes": [
448
+ "setup-push",
449
+ "validate-setup"
450
+ ]
451
+ },
452
+ "enforcement": {
453
+ "deterministic": [
454
+ {
455
+ "check": "validator-finding-absent",
456
+ "finding_rule_id": "android.notifications.amity-preferences-configured"
457
+ }
458
+ ],
459
+ "attestation": {
460
+ "allowed": true,
461
+ "host_agent_min_confidence": "high",
462
+ "human_allowed": true,
463
+ "evidence_required": [
464
+ {
465
+ "field": "preference_sync",
466
+ "description": "How the component ensures Amity server-side notification preferences are fetched or respected.",
467
+ "upload_policy": "upload-with-consent"
468
+ }
469
+ ]
470
+ }
471
+ }
472
+ },
473
+ {
474
+ "id": "flutter.notifications.amity-preferences-configured",
475
+ "version": 1,
476
+ "title": "Flutter Amity notification preferences must be configured",
477
+ "severity": "warning",
478
+ "rationale": "Amity exposes server-side notification preferences (mute community/user, per-event toggles) separate from firebase_messaging push. After AmityCoreClient.registerPushNotification(token), await AmityCoreClient.notifications().getSettings() — typically inside the same FirebaseMessaging.instance.onTokenRefresh listener — so the Dart session respects the user's mutes.",
479
+ "applies_when": {
480
+ "platforms": [
481
+ "flutter"
482
+ ],
483
+ "outcomes": [
484
+ "setup-push",
485
+ "validate-setup"
486
+ ]
487
+ },
488
+ "enforcement": {
489
+ "deterministic": [
490
+ {
491
+ "check": "validator-finding-absent",
492
+ "finding_rule_id": "flutter.notifications.amity-preferences-configured"
493
+ }
494
+ ],
495
+ "attestation": {
496
+ "allowed": true,
497
+ "host_agent_min_confidence": "high",
498
+ "human_allowed": true,
499
+ "evidence_required": [
500
+ {
501
+ "field": "preference_sync",
502
+ "description": "How the component ensures Amity server-side notification preferences are fetched or respected.",
503
+ "upload_policy": "upload-with-consent"
504
+ }
505
+ ]
506
+ }
507
+ }
508
+ },
509
+ {
510
+ "id": "ios.notifications.amity-preferences-configured",
511
+ "version": 1,
512
+ "title": "iOS Amity notification preferences must be configured",
513
+ "severity": "warning",
514
+ "rationale": "Amity exposes server-side notification preferences (mute community/user, per-event toggles) separate from APNS. After registerPushNotification(withDeviceToken:) in didRegisterForRemoteNotificationsWithDeviceToken, call client.notifications().getSettings { ... } so the running Swift session respects the user's mutes instead of firing every APNS payload.",
515
+ "applies_when": {
516
+ "platforms": [
517
+ "ios"
518
+ ],
519
+ "outcomes": [
520
+ "setup-push",
521
+ "validate-setup"
522
+ ]
523
+ },
524
+ "enforcement": {
525
+ "deterministic": [
526
+ {
527
+ "check": "validator-finding-absent",
528
+ "finding_rule_id": "ios.notifications.amity-preferences-configured"
529
+ }
530
+ ],
531
+ "attestation": {
532
+ "allowed": true,
533
+ "host_agent_min_confidence": "high",
534
+ "human_allowed": true,
535
+ "evidence_required": [
536
+ {
537
+ "field": "preference_sync",
538
+ "description": "How the component ensures Amity server-side notification preferences are fetched or respected.",
539
+ "upload_policy": "upload-with-consent"
540
+ }
541
+ ]
542
+ }
543
+ }
93
544
  }
94
545
  ]
95
546
  }
@@ -417,6 +417,66 @@
417
417
  "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "react-native.auth.no-literal-user-id" }],
418
418
  "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "identity_source", "description": "Where the runtime userId comes from (auth context, secure storage, etc.).", "upload_policy": "upload-with-consent" }] }
419
419
  }
420
+ },
421
+ {
422
+ "id": "android.session-handler.retained",
423
+ "version": 1,
424
+ "title": "Android session handler must be retained",
425
+ "severity": "warning",
426
+ "rationale": "AmitySessionHandler declared as a function-local variable gets garbage collected. The SDK's renewal callback will then call into nothing, causing sessions to silently expire.",
427
+ "applies_when": { "platforms": ["android"], "outcomes": ["setup-sdk", "validate-setup"] },
428
+ "enforcement": {
429
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "android.session-handler.retained" }],
430
+ "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "handler_retention", "description": "Where the handler is owned to prevent GC.", "upload_policy": "upload-with-consent" }] }
431
+ }
432
+ },
433
+ {
434
+ "id": "flutter.session-handler.retained",
435
+ "version": 1,
436
+ "title": "Flutter session handler must be retained",
437
+ "severity": "warning",
438
+ "rationale": "AmitySessionHandler declared as a function-local variable gets garbage collected by Dart. The SDK's renewal callback will then call into nothing, causing sessions to silently expire.",
439
+ "applies_when": { "platforms": ["flutter"], "outcomes": ["setup-sdk", "validate-setup"] },
440
+ "enforcement": {
441
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "flutter.session-handler.retained" }],
442
+ "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "handler_retention", "description": "Where the handler is owned to prevent GC.", "upload_policy": "upload-with-consent" }] }
443
+ }
444
+ },
445
+ {
446
+ "id": "ios.session-handler.retained",
447
+ "version": 1,
448
+ "title": "iOS session handler must be retained",
449
+ "severity": "warning",
450
+ "rationale": "AmitySessionHandler declared as a function-local variable will be released by ARC. The SDK's renewal callback will then call into nothing, causing sessions to silently expire.",
451
+ "applies_when": { "platforms": ["ios"], "outcomes": ["setup-sdk", "validate-setup"] },
452
+ "enforcement": {
453
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "ios.session-handler.retained" }],
454
+ "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "handler_retention", "description": "Where the handler is owned to prevent GC.", "upload_policy": "upload-with-consent" }] }
455
+ }
456
+ },
457
+ {
458
+ "id": "typescript.session-handler.retained",
459
+ "version": 1,
460
+ "title": "TypeScript session handler must be retained",
461
+ "severity": "warning",
462
+ "rationale": "Session handler declared as a function-local variable gets garbage collected by JS. The SDK's renewal callback will then call into nothing, causing sessions to silently expire.",
463
+ "applies_when": { "platforms": ["typescript"], "outcomes": ["setup-sdk", "validate-setup"] },
464
+ "enforcement": {
465
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "typescript.session-handler.retained" }],
466
+ "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "handler_retention", "description": "Where the handler is owned to prevent GC.", "upload_policy": "upload-with-consent" }] }
467
+ }
468
+ },
469
+ {
470
+ "id": "react-native.session-handler.retained",
471
+ "version": 1,
472
+ "title": "React Native session handler must be retained",
473
+ "severity": "warning",
474
+ "rationale": "Session handler declared as a function-local variable gets garbage collected by JS. The SDK's renewal callback will then call into nothing, causing sessions to silently expire.",
475
+ "applies_when": { "platforms": ["react-native"], "outcomes": ["setup-sdk", "validate-setup"] },
476
+ "enforcement": {
477
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "react-native.session-handler.retained" }],
478
+ "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "handler_retention", "description": "Where the handler is owned to prevent GC.", "upload_policy": "upload-with-consent" }] }
479
+ }
420
480
  }
421
481
  ]
422
482
  }