@amityco/social-plus-vise 0.11.0 → 0.12.2
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.
- package/CHANGELOG.md +58 -0
- package/README.md +32 -1
- package/dist/capabilities.js +447 -0
- package/dist/outcomes.js +444 -2
- package/dist/server.js +76 -3
- package/dist/tools/ast.js +25 -0
- package/dist/tools/compliance.js +20 -0
- package/dist/tools/design.js +1496 -0
- package/dist/tools/docs.js +9 -4
- package/dist/tools/integration.js +83 -7
- package/dist/tools/project.js +652 -44
- package/dist/tools/sdkVersion.js +129 -0
- package/package.json +16 -3
- package/rules/comments.yaml +0 -72
- package/rules/feed.yaml +1150 -11
- package/rules/sdk-lifecycle.yaml +9 -9
- package/skills/social-plus-vise/SKILL.md +93 -93
- package/skills/social-plus-vise/reference/debugging.md +39 -0
- package/skills/social-plus-vise/reference/operations.md +59 -0
package/rules/feed.yaml
CHANGED
|
@@ -1874,7 +1874,7 @@
|
|
|
1874
1874
|
"version": 1,
|
|
1875
1875
|
"title": "React Native unread count must use subscribed stream",
|
|
1876
1876
|
"severity": "warning",
|
|
1877
|
-
"rationale": "Amity exposes a server-synced unreadCount stream. Hand-rolling messages.filter(m => !m.isRead).length in a hook drifts when the user reads on another device
|
|
1877
|
+
"rationale": "Amity exposes a server-synced unreadCount stream. Hand-rolling messages.filter(m => !m.isRead).length in a hook drifts when the user reads on another device \u2014 the bridge never pushes back to your local array. Subscribe via AmityCoreClient.unreadCount() and bind it to a useState in the badge component.",
|
|
1878
1878
|
"applies_when": {
|
|
1879
1879
|
"platforms": [
|
|
1880
1880
|
"react-native"
|
|
@@ -1911,7 +1911,7 @@
|
|
|
1911
1911
|
"version": 1,
|
|
1912
1912
|
"title": "Android unread count must use subscribed stream",
|
|
1913
1913
|
"severity": "warning",
|
|
1914
|
-
"rationale": "Amity exposes a server-synced unreadCount stream. Kotlin code that counts via posts.count { !it.isRead } in a ViewModel drifts when the user reads from another device
|
|
1914
|
+
"rationale": "Amity exposes a server-synced unreadCount stream. Kotlin code that counts via posts.count { !it.isRead } in a ViewModel drifts when the user reads from another device \u2014 Room/Flow caches never get the server-side ack. Collect AmityCoreClient.unreadCount() as a Flow<Int> and expose it as StateFlow to the UI.",
|
|
1915
1915
|
"applies_when": {
|
|
1916
1916
|
"platforms": [
|
|
1917
1917
|
"android"
|
|
@@ -1948,7 +1948,7 @@
|
|
|
1948
1948
|
"version": 1,
|
|
1949
1949
|
"title": "Flutter unread count must use subscribed stream",
|
|
1950
1950
|
"severity": "warning",
|
|
1951
|
-
"rationale": "Amity exposes a server-synced unreadCount stream. Dart code that counts via posts.where((p) => !p.isRead).length inside a setState drifts when the user reads from another device
|
|
1951
|
+
"rationale": "Amity exposes a server-synced unreadCount stream. Dart code that counts via posts.where((p) => !p.isRead).length inside a setState drifts when the user reads from another device \u2014 local List<AmityPost> never receives the server ack. Use a StreamBuilder bound to AmityCoreClient.unreadCount() for the badge.",
|
|
1952
1952
|
"applies_when": {
|
|
1953
1953
|
"platforms": [
|
|
1954
1954
|
"flutter"
|
|
@@ -1985,7 +1985,7 @@
|
|
|
1985
1985
|
"version": 1,
|
|
1986
1986
|
"title": "iOS unread count must use subscribed stream",
|
|
1987
1987
|
"severity": "warning",
|
|
1988
|
-
"rationale": "Amity exposes a server-synced unreadCount stream. Swift code that counts via posts.filter { !$0.isRead }.count inside an @State drifts when the user reads from another device
|
|
1988
|
+
"rationale": "Amity exposes a server-synced unreadCount stream. Swift code that counts via posts.filter { !$0.isRead }.count inside an @State drifts when the user reads from another device \u2014 the local snapshot never receives the server ack. Bind the SwiftUI badge to client.unreadCount() via @ObservedObject or Combine.",
|
|
1989
1989
|
"applies_when": {
|
|
1990
1990
|
"platforms": [
|
|
1991
1991
|
"ios"
|
|
@@ -2022,7 +2022,7 @@
|
|
|
2022
2022
|
"version": 1,
|
|
2023
2023
|
"title": "TypeScript media must be uploaded via Amity file client",
|
|
2024
2024
|
"severity": "warning",
|
|
2025
|
-
"rationale": "Pushing a File or Blob to S3/Cloudinary and passing the resulting URL into createPost({ attachments: [{ url }] }) fails
|
|
2025
|
+
"rationale": "Pushing a File or Blob to S3/Cloudinary and passing the resulting URL into createPost({ attachments: [{ url }] }) fails \u2014 the SDK only recognizes AmityFileIds from its own pipeline. Upload via AmityFileRepository.uploadFile(file) (or the upload hook in your Amity wrapper) and attach { fileId } from that response instead.",
|
|
2026
2026
|
"applies_when": {
|
|
2027
2027
|
"platforms": [
|
|
2028
2028
|
"typescript"
|
|
@@ -2058,7 +2058,7 @@
|
|
|
2058
2058
|
"version": 1,
|
|
2059
2059
|
"title": "React Native media must be uploaded via Amity file client",
|
|
2060
2060
|
"severity": "warning",
|
|
2061
|
-
"rationale": "Picking an image via react-native-image-picker, uploading the local URI to S3, and passing the S3 URL into createPost fails
|
|
2061
|
+
"rationale": "Picking an image via react-native-image-picker, uploading the local URI to S3, and passing the S3 URL into createPost fails \u2014 the SDK only recognizes AmityFileIds from its own pipeline. Upload via AmityFileRepository.uploadFile(localUri) (typically inside a useMutation) and attach { fileId } from that response.",
|
|
2062
2062
|
"applies_when": {
|
|
2063
2063
|
"platforms": [
|
|
2064
2064
|
"react-native"
|
|
@@ -2094,7 +2094,7 @@
|
|
|
2094
2094
|
"version": 1,
|
|
2095
2095
|
"title": "Android media must be uploaded via Amity file client",
|
|
2096
2096
|
"severity": "warning",
|
|
2097
|
-
"rationale": "Picking a Uri from MediaStore, uploading via OkHttp to a custom bucket, and passing the bucket URL into createPost fails
|
|
2097
|
+
"rationale": "Picking a Uri from MediaStore, uploading via OkHttp to a custom bucket, and passing the bucket URL into createPost fails \u2014 the SDK only recognizes AmityFileIds from AmityFileRepository.uploadFile(uri). Run the upload in viewModelScope, await the resulting AmityFile, then attach its fileId to the post builder.",
|
|
2098
2098
|
"applies_when": {
|
|
2099
2099
|
"platforms": [
|
|
2100
2100
|
"android"
|
|
@@ -2130,7 +2130,7 @@
|
|
|
2130
2130
|
"version": 1,
|
|
2131
2131
|
"title": "Flutter media must be uploaded via Amity file client",
|
|
2132
2132
|
"severity": "warning",
|
|
2133
|
-
"rationale": "Picking via image_picker, posting the File to a custom bucket, and passing the resulting URL into createPost fails
|
|
2133
|
+
"rationale": "Picking via image_picker, posting the File to a custom bucket, and passing the resulting URL into createPost fails \u2014 the SDK only recognizes AmityFileIds from AmityFileRepository.uploadFile(File). Await the upload Future, then attach the returned AmityImageData/AmityVideoData by fileId.",
|
|
2134
2134
|
"applies_when": {
|
|
2135
2135
|
"platforms": [
|
|
2136
2136
|
"flutter"
|
|
@@ -2166,7 +2166,7 @@
|
|
|
2166
2166
|
"version": 1,
|
|
2167
2167
|
"title": "iOS media must be uploaded via Amity file client",
|
|
2168
2168
|
"severity": "warning",
|
|
2169
|
-
"rationale": "Picking a PHAsset/UIImage, uploading via URLSession to a custom bucket, and passing the resulting URL into createPost fails
|
|
2169
|
+
"rationale": "Picking a PHAsset/UIImage, uploading via URLSession to a custom bucket, and passing the resulting URL into createPost fails \u2014 the SDK only recognizes AmityFileIds from AmityFileRepository.uploadFile(_:). Use the async/await upload variant, then attach AmityImageData(fileId:) or AmityVideoData(fileId:) from the result.",
|
|
2170
2170
|
"applies_when": {
|
|
2171
2171
|
"platforms": [
|
|
2172
2172
|
"ios"
|
|
@@ -2376,6 +2376,1145 @@
|
|
|
2376
2376
|
]
|
|
2377
2377
|
}
|
|
2378
2378
|
}
|
|
2379
|
-
}
|
|
2379
|
+
},
|
|
2380
|
+
{
|
|
2381
|
+
"id": "typescript.feed.post-datatype-handled",
|
|
2382
|
+
"version": 1,
|
|
2383
|
+
"title": "Typescript post renderer must handle all post data types",
|
|
2384
|
+
"severity": "warning",
|
|
2385
|
+
"rationale": "getGlobalFeed and getPosts return every post type (text, image, video, file, poll, liveStream). A renderer that only reads the text field leaves all other content types silently blank, resulting in empty cards in the feed.",
|
|
2386
|
+
"applies_when": {
|
|
2387
|
+
"platforms": [
|
|
2388
|
+
"typescript"
|
|
2389
|
+
],
|
|
2390
|
+
"outcomes": [
|
|
2391
|
+
"add-feed",
|
|
2392
|
+
"validate-setup"
|
|
2393
|
+
]
|
|
2394
|
+
},
|
|
2395
|
+
"enforcement": {
|
|
2396
|
+
"deterministic": [
|
|
2397
|
+
{
|
|
2398
|
+
"check": "validator-finding-absent",
|
|
2399
|
+
"finding_rule_id": "typescript.feed.post-datatype-handled"
|
|
2400
|
+
}
|
|
2401
|
+
],
|
|
2402
|
+
"attestation": {
|
|
2403
|
+
"allowed": true,
|
|
2404
|
+
"host_agent_min_confidence": "high",
|
|
2405
|
+
"human_allowed": true,
|
|
2406
|
+
"evidence_required": [
|
|
2407
|
+
{
|
|
2408
|
+
"field": "datatype_handling",
|
|
2409
|
+
"description": "How the renderer branches on post.dataType to handle each content type.",
|
|
2410
|
+
"upload_policy": "upload-with-consent"
|
|
2411
|
+
}
|
|
2412
|
+
]
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
},
|
|
2416
|
+
{
|
|
2417
|
+
"id": "react-native.feed.post-datatype-handled",
|
|
2418
|
+
"version": 1,
|
|
2419
|
+
"title": "React-Native post renderer must handle all post data types",
|
|
2420
|
+
"severity": "warning",
|
|
2421
|
+
"rationale": "getGlobalFeed and getPosts return every post type (text, image, video, file, poll, liveStream). A renderer that only reads the text field leaves all other content types silently blank, resulting in empty cards in the feed.",
|
|
2422
|
+
"applies_when": {
|
|
2423
|
+
"platforms": [
|
|
2424
|
+
"react-native"
|
|
2425
|
+
],
|
|
2426
|
+
"outcomes": [
|
|
2427
|
+
"add-feed",
|
|
2428
|
+
"validate-setup"
|
|
2429
|
+
]
|
|
2430
|
+
},
|
|
2431
|
+
"enforcement": {
|
|
2432
|
+
"deterministic": [
|
|
2433
|
+
{
|
|
2434
|
+
"check": "validator-finding-absent",
|
|
2435
|
+
"finding_rule_id": "react-native.feed.post-datatype-handled"
|
|
2436
|
+
}
|
|
2437
|
+
],
|
|
2438
|
+
"attestation": {
|
|
2439
|
+
"allowed": true,
|
|
2440
|
+
"host_agent_min_confidence": "high",
|
|
2441
|
+
"human_allowed": true,
|
|
2442
|
+
"evidence_required": [
|
|
2443
|
+
{
|
|
2444
|
+
"field": "datatype_handling",
|
|
2445
|
+
"description": "How the renderer branches on post.dataType to handle each content type.",
|
|
2446
|
+
"upload_policy": "upload-with-consent"
|
|
2447
|
+
}
|
|
2448
|
+
]
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
},
|
|
2452
|
+
{
|
|
2453
|
+
"id": "flutter.feed.post-datatype-handled",
|
|
2454
|
+
"version": 1,
|
|
2455
|
+
"title": "Flutter post renderer must handle all post data types",
|
|
2456
|
+
"severity": "warning",
|
|
2457
|
+
"rationale": "getGlobalFeed and getPosts return every post type (text, image, video, file, poll, liveStream). A renderer that only reads the text field leaves all other content types silently blank, resulting in empty cards in the feed.",
|
|
2458
|
+
"applies_when": {
|
|
2459
|
+
"platforms": [
|
|
2460
|
+
"flutter"
|
|
2461
|
+
],
|
|
2462
|
+
"outcomes": [
|
|
2463
|
+
"add-feed",
|
|
2464
|
+
"validate-setup"
|
|
2465
|
+
]
|
|
2466
|
+
},
|
|
2467
|
+
"enforcement": {
|
|
2468
|
+
"deterministic": [
|
|
2469
|
+
{
|
|
2470
|
+
"check": "validator-finding-absent",
|
|
2471
|
+
"finding_rule_id": "flutter.feed.post-datatype-handled"
|
|
2472
|
+
}
|
|
2473
|
+
],
|
|
2474
|
+
"attestation": {
|
|
2475
|
+
"allowed": true,
|
|
2476
|
+
"host_agent_min_confidence": "high",
|
|
2477
|
+
"human_allowed": true,
|
|
2478
|
+
"evidence_required": [
|
|
2479
|
+
{
|
|
2480
|
+
"field": "datatype_handling",
|
|
2481
|
+
"description": "How the renderer branches on post.dataType to handle each content type.",
|
|
2482
|
+
"upload_policy": "upload-with-consent"
|
|
2483
|
+
}
|
|
2484
|
+
]
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
},
|
|
2488
|
+
{
|
|
2489
|
+
"id": "ios.feed.post-datatype-handled",
|
|
2490
|
+
"version": 1,
|
|
2491
|
+
"title": "Ios post renderer must handle all post data types",
|
|
2492
|
+
"severity": "warning",
|
|
2493
|
+
"rationale": "getGlobalFeed and getPosts return every post type (text, image, video, file, poll, liveStream). A renderer that only reads the text field leaves all other content types silently blank, resulting in empty cards in the feed.",
|
|
2494
|
+
"applies_when": {
|
|
2495
|
+
"platforms": [
|
|
2496
|
+
"ios"
|
|
2497
|
+
],
|
|
2498
|
+
"outcomes": [
|
|
2499
|
+
"add-feed",
|
|
2500
|
+
"validate-setup"
|
|
2501
|
+
]
|
|
2502
|
+
},
|
|
2503
|
+
"enforcement": {
|
|
2504
|
+
"deterministic": [
|
|
2505
|
+
{
|
|
2506
|
+
"check": "validator-finding-absent",
|
|
2507
|
+
"finding_rule_id": "ios.feed.post-datatype-handled"
|
|
2508
|
+
}
|
|
2509
|
+
],
|
|
2510
|
+
"attestation": {
|
|
2511
|
+
"allowed": true,
|
|
2512
|
+
"host_agent_min_confidence": "high",
|
|
2513
|
+
"human_allowed": true,
|
|
2514
|
+
"evidence_required": [
|
|
2515
|
+
{
|
|
2516
|
+
"field": "datatype_handling",
|
|
2517
|
+
"description": "How the renderer branches on post.dataType to handle each content type.",
|
|
2518
|
+
"upload_policy": "upload-with-consent"
|
|
2519
|
+
}
|
|
2520
|
+
]
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
},
|
|
2524
|
+
{
|
|
2525
|
+
"id": "android.feed.post-datatype-handled",
|
|
2526
|
+
"version": 1,
|
|
2527
|
+
"title": "Android post renderer must handle all post data types",
|
|
2528
|
+
"severity": "warning",
|
|
2529
|
+
"rationale": "getGlobalFeed and getPosts return every post type (text, image, video, file, poll, liveStream). A renderer that only reads the text field leaves all other content types silently blank, resulting in empty cards in the feed.",
|
|
2530
|
+
"applies_when": {
|
|
2531
|
+
"platforms": [
|
|
2532
|
+
"android"
|
|
2533
|
+
],
|
|
2534
|
+
"outcomes": [
|
|
2535
|
+
"add-feed",
|
|
2536
|
+
"validate-setup"
|
|
2537
|
+
]
|
|
2538
|
+
},
|
|
2539
|
+
"enforcement": {
|
|
2540
|
+
"deterministic": [
|
|
2541
|
+
{
|
|
2542
|
+
"check": "validator-finding-absent",
|
|
2543
|
+
"finding_rule_id": "android.feed.post-datatype-handled"
|
|
2544
|
+
}
|
|
2545
|
+
],
|
|
2546
|
+
"attestation": {
|
|
2547
|
+
"allowed": true,
|
|
2548
|
+
"host_agent_min_confidence": "high",
|
|
2549
|
+
"human_allowed": true,
|
|
2550
|
+
"evidence_required": [
|
|
2551
|
+
{
|
|
2552
|
+
"field": "datatype_handling",
|
|
2553
|
+
"description": "How the renderer branches on post.dataType to handle each content type.",
|
|
2554
|
+
"upload_policy": "upload-with-consent"
|
|
2555
|
+
}
|
|
2556
|
+
]
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
},
|
|
2560
|
+
{
|
|
2561
|
+
"id": "typescript.community.avatar-from-sdk",
|
|
2562
|
+
"version": 1,
|
|
2563
|
+
"title": "Typescript community/user display must use SDK-provided avatar URL",
|
|
2564
|
+
"severity": "warning",
|
|
2565
|
+
"rationale": "The SDK resolves the avatar URL directly on Community and User objects (avatar.fileUrl / avatarImage / getAvatar()). Using a string initial derived from the display name ignores photos uploaded by the user.",
|
|
2566
|
+
"applies_when": {
|
|
2567
|
+
"platforms": [
|
|
2568
|
+
"typescript"
|
|
2569
|
+
],
|
|
2570
|
+
"outcomes": [
|
|
2571
|
+
"add-feed",
|
|
2572
|
+
"add-comments",
|
|
2573
|
+
"validate-setup"
|
|
2574
|
+
]
|
|
2575
|
+
},
|
|
2576
|
+
"enforcement": {
|
|
2577
|
+
"deterministic": [
|
|
2578
|
+
{
|
|
2579
|
+
"check": "validator-finding-absent",
|
|
2580
|
+
"finding_rule_id": "typescript.community.avatar-from-sdk"
|
|
2581
|
+
}
|
|
2582
|
+
],
|
|
2583
|
+
"attestation": {
|
|
2584
|
+
"allowed": true,
|
|
2585
|
+
"host_agent_min_confidence": "high",
|
|
2586
|
+
"human_allowed": true,
|
|
2587
|
+
"evidence_required": [
|
|
2588
|
+
{
|
|
2589
|
+
"field": "avatar_rendering",
|
|
2590
|
+
"description": "How the community or user avatar URL is read from the SDK object and rendered.",
|
|
2591
|
+
"upload_policy": "upload-with-consent"
|
|
2592
|
+
}
|
|
2593
|
+
]
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
},
|
|
2597
|
+
{
|
|
2598
|
+
"id": "react-native.community.avatar-from-sdk",
|
|
2599
|
+
"version": 1,
|
|
2600
|
+
"title": "React-Native community/user display must use SDK-provided avatar URL",
|
|
2601
|
+
"severity": "warning",
|
|
2602
|
+
"rationale": "The SDK resolves the avatar URL directly on Community and User objects (avatar.fileUrl / avatarImage / getAvatar()). Using a string initial derived from the display name ignores photos uploaded by the user.",
|
|
2603
|
+
"applies_when": {
|
|
2604
|
+
"platforms": [
|
|
2605
|
+
"react-native"
|
|
2606
|
+
],
|
|
2607
|
+
"outcomes": [
|
|
2608
|
+
"add-feed",
|
|
2609
|
+
"add-comments",
|
|
2610
|
+
"validate-setup"
|
|
2611
|
+
]
|
|
2612
|
+
},
|
|
2613
|
+
"enforcement": {
|
|
2614
|
+
"deterministic": [
|
|
2615
|
+
{
|
|
2616
|
+
"check": "validator-finding-absent",
|
|
2617
|
+
"finding_rule_id": "react-native.community.avatar-from-sdk"
|
|
2618
|
+
}
|
|
2619
|
+
],
|
|
2620
|
+
"attestation": {
|
|
2621
|
+
"allowed": true,
|
|
2622
|
+
"host_agent_min_confidence": "high",
|
|
2623
|
+
"human_allowed": true,
|
|
2624
|
+
"evidence_required": [
|
|
2625
|
+
{
|
|
2626
|
+
"field": "avatar_rendering",
|
|
2627
|
+
"description": "How the community or user avatar URL is read from the SDK object and rendered.",
|
|
2628
|
+
"upload_policy": "upload-with-consent"
|
|
2629
|
+
}
|
|
2630
|
+
]
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
"id": "flutter.community.avatar-from-sdk",
|
|
2636
|
+
"version": 1,
|
|
2637
|
+
"title": "Flutter community/user display must use SDK-provided avatar URL",
|
|
2638
|
+
"severity": "warning",
|
|
2639
|
+
"rationale": "The SDK resolves the avatar URL directly on Community and User objects (avatar.fileUrl / avatarImage / getAvatar()). Using a string initial derived from the display name ignores photos uploaded by the user.",
|
|
2640
|
+
"applies_when": {
|
|
2641
|
+
"platforms": [
|
|
2642
|
+
"flutter"
|
|
2643
|
+
],
|
|
2644
|
+
"outcomes": [
|
|
2645
|
+
"add-feed",
|
|
2646
|
+
"add-comments",
|
|
2647
|
+
"validate-setup"
|
|
2648
|
+
]
|
|
2649
|
+
},
|
|
2650
|
+
"enforcement": {
|
|
2651
|
+
"deterministic": [
|
|
2652
|
+
{
|
|
2653
|
+
"check": "validator-finding-absent",
|
|
2654
|
+
"finding_rule_id": "flutter.community.avatar-from-sdk"
|
|
2655
|
+
}
|
|
2656
|
+
],
|
|
2657
|
+
"attestation": {
|
|
2658
|
+
"allowed": true,
|
|
2659
|
+
"host_agent_min_confidence": "high",
|
|
2660
|
+
"human_allowed": true,
|
|
2661
|
+
"evidence_required": [
|
|
2662
|
+
{
|
|
2663
|
+
"field": "avatar_rendering",
|
|
2664
|
+
"description": "How the community or user avatar URL is read from the SDK object and rendered.",
|
|
2665
|
+
"upload_policy": "upload-with-consent"
|
|
2666
|
+
}
|
|
2667
|
+
]
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
},
|
|
2671
|
+
{
|
|
2672
|
+
"id": "ios.community.avatar-from-sdk",
|
|
2673
|
+
"version": 1,
|
|
2674
|
+
"title": "Ios community/user display must use SDK-provided avatar URL",
|
|
2675
|
+
"severity": "warning",
|
|
2676
|
+
"rationale": "The SDK resolves the avatar URL directly on Community and User objects (avatar.fileUrl / avatarImage / getAvatar()). Using a string initial derived from the display name ignores photos uploaded by the user.",
|
|
2677
|
+
"applies_when": {
|
|
2678
|
+
"platforms": [
|
|
2679
|
+
"ios"
|
|
2680
|
+
],
|
|
2681
|
+
"outcomes": [
|
|
2682
|
+
"add-feed",
|
|
2683
|
+
"add-comments",
|
|
2684
|
+
"validate-setup"
|
|
2685
|
+
]
|
|
2686
|
+
},
|
|
2687
|
+
"enforcement": {
|
|
2688
|
+
"deterministic": [
|
|
2689
|
+
{
|
|
2690
|
+
"check": "validator-finding-absent",
|
|
2691
|
+
"finding_rule_id": "ios.community.avatar-from-sdk"
|
|
2692
|
+
}
|
|
2693
|
+
],
|
|
2694
|
+
"attestation": {
|
|
2695
|
+
"allowed": true,
|
|
2696
|
+
"host_agent_min_confidence": "high",
|
|
2697
|
+
"human_allowed": true,
|
|
2698
|
+
"evidence_required": [
|
|
2699
|
+
{
|
|
2700
|
+
"field": "avatar_rendering",
|
|
2701
|
+
"description": "How the community or user avatar URL is read from the SDK object and rendered.",
|
|
2702
|
+
"upload_policy": "upload-with-consent"
|
|
2703
|
+
}
|
|
2704
|
+
]
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
},
|
|
2708
|
+
{
|
|
2709
|
+
"id": "android.community.avatar-from-sdk",
|
|
2710
|
+
"version": 1,
|
|
2711
|
+
"title": "Android community/user display must use SDK-provided avatar URL",
|
|
2712
|
+
"severity": "warning",
|
|
2713
|
+
"rationale": "The SDK resolves the avatar URL directly on Community and User objects (avatar.fileUrl / avatarImage / getAvatar()). Using a string initial derived from the display name ignores photos uploaded by the user.",
|
|
2714
|
+
"applies_when": {
|
|
2715
|
+
"platforms": [
|
|
2716
|
+
"android"
|
|
2717
|
+
],
|
|
2718
|
+
"outcomes": [
|
|
2719
|
+
"add-feed",
|
|
2720
|
+
"add-comments",
|
|
2721
|
+
"validate-setup"
|
|
2722
|
+
]
|
|
2723
|
+
},
|
|
2724
|
+
"enforcement": {
|
|
2725
|
+
"deterministic": [
|
|
2726
|
+
{
|
|
2727
|
+
"check": "validator-finding-absent",
|
|
2728
|
+
"finding_rule_id": "android.community.avatar-from-sdk"
|
|
2729
|
+
}
|
|
2730
|
+
],
|
|
2731
|
+
"attestation": {
|
|
2732
|
+
"allowed": true,
|
|
2733
|
+
"host_agent_min_confidence": "high",
|
|
2734
|
+
"human_allowed": true,
|
|
2735
|
+
"evidence_required": [
|
|
2736
|
+
{
|
|
2737
|
+
"field": "avatar_rendering",
|
|
2738
|
+
"description": "How the community or user avatar URL is read from the SDK object and rendered.",
|
|
2739
|
+
"upload_policy": "upload-with-consent"
|
|
2740
|
+
}
|
|
2741
|
+
]
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
},
|
|
2745
|
+
{
|
|
2746
|
+
"id": "typescript.feed.poll-answer-data-shape",
|
|
2747
|
+
"version": 1,
|
|
2748
|
+
"title": "TypeScript poll answer text must be read from answer.data directly (plain string)",
|
|
2749
|
+
"severity": "warning",
|
|
2750
|
+
"rationale": "Amity.PollAnswer.data is a plain string containing the answer text, not an object. Accessing .data.text or casting to {text?: string} returns undefined, silently breaking poll rendering.",
|
|
2751
|
+
"applies_when": {
|
|
2752
|
+
"platforms": ["typescript"],
|
|
2753
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
2754
|
+
},
|
|
2755
|
+
"enforcement": {
|
|
2756
|
+
"deterministic": [
|
|
2757
|
+
{
|
|
2758
|
+
"check": "validator-finding-absent",
|
|
2759
|
+
"finding_rule_id": "typescript.feed.poll-answer-data-shape"
|
|
2760
|
+
}
|
|
2761
|
+
],
|
|
2762
|
+
"attestation": {
|
|
2763
|
+
"allowed": true,
|
|
2764
|
+
"host_agent_min_confidence": "high",
|
|
2765
|
+
"human_allowed": true,
|
|
2766
|
+
"evidence_required": [
|
|
2767
|
+
{
|
|
2768
|
+
"field": "poll_answer_rendering",
|
|
2769
|
+
"description": "How poll answer text is read from the SDK answer object and rendered.",
|
|
2770
|
+
"upload_policy": "upload-with-consent"
|
|
2771
|
+
}
|
|
2772
|
+
]
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
},
|
|
2776
|
+
{
|
|
2777
|
+
"id": "typescript.comments.query-has-limit",
|
|
2778
|
+
"version": 1,
|
|
2779
|
+
"title": "TypeScript comment query must include an explicit limit parameter",
|
|
2780
|
+
"severity": "warning",
|
|
2781
|
+
"rationale": "CommentRepository.getComments without an explicit limit triggers the SDK's default pagination which uses skip/limit — incompatible with the server's scrollable query type. Runtime error 500000: 'skip, limit and sorting are not supported in query type scrollable'.",
|
|
2782
|
+
"applies_when": {
|
|
2783
|
+
"platforms": ["typescript"],
|
|
2784
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
2785
|
+
},
|
|
2786
|
+
"enforcement": {
|
|
2787
|
+
"deterministic": [
|
|
2788
|
+
{
|
|
2789
|
+
"check": "validator-finding-absent",
|
|
2790
|
+
"finding_rule_id": "typescript.comments.query-has-limit"
|
|
2791
|
+
}
|
|
2792
|
+
],
|
|
2793
|
+
"attestation": {
|
|
2794
|
+
"allowed": true,
|
|
2795
|
+
"host_agent_min_confidence": "high",
|
|
2796
|
+
"human_allowed": true,
|
|
2797
|
+
"evidence_required": [
|
|
2798
|
+
{
|
|
2799
|
+
"field": "comment_query_params",
|
|
2800
|
+
"description": "The parameters passed to the comment live collection query, including limit/pageSize.",
|
|
2801
|
+
"upload_policy": "upload-with-consent"
|
|
2802
|
+
}
|
|
2803
|
+
]
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
},
|
|
2807
|
+
{
|
|
2808
|
+
"id": "typescript.community.display-name-from-sdk",
|
|
2809
|
+
"version": 1,
|
|
2810
|
+
"title": "TypeScript community display name must come from the SDK, not from a raw ID",
|
|
2811
|
+
"severity": "warning",
|
|
2812
|
+
"rationale": "Using a raw communityId as a displayName fallback (e.g. when passing only the ID in navigation state) shows a UUID to users instead of the community's actual name.",
|
|
2813
|
+
"applies_when": {
|
|
2814
|
+
"platforms": ["typescript"],
|
|
2815
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
2816
|
+
},
|
|
2817
|
+
"enforcement": {
|
|
2818
|
+
"deterministic": [
|
|
2819
|
+
{
|
|
2820
|
+
"check": "validator-finding-absent",
|
|
2821
|
+
"finding_rule_id": "typescript.community.display-name-from-sdk"
|
|
2822
|
+
}
|
|
2823
|
+
],
|
|
2824
|
+
"attestation": {
|
|
2825
|
+
"allowed": true,
|
|
2826
|
+
"host_agent_min_confidence": "high",
|
|
2827
|
+
"human_allowed": true,
|
|
2828
|
+
"evidence_required": [
|
|
2829
|
+
{
|
|
2830
|
+
"field": "community_name_source",
|
|
2831
|
+
"description": "Where the community display name is sourced from when rendering community references.",
|
|
2832
|
+
"upload_policy": "upload-with-consent"
|
|
2833
|
+
}
|
|
2834
|
+
]
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
},
|
|
2838
|
+
{
|
|
2839
|
+
"id": "typescript.feed.room-post-fetched",
|
|
2840
|
+
"version": 1,
|
|
2841
|
+
"title": "TypeScript room post must fetch room title via RoomRepository, not display raw roomId",
|
|
2842
|
+
"severity": "warning",
|
|
2843
|
+
"rationale": "Room is a first-class SDK entity with a title field. Displaying room.roomId as the room name shows a UUID. Use RoomRepository.getRoom to get room.title and room.status.",
|
|
2844
|
+
"applies_when": {
|
|
2845
|
+
"platforms": ["typescript"],
|
|
2846
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
2847
|
+
},
|
|
2848
|
+
"enforcement": {
|
|
2849
|
+
"deterministic": [
|
|
2850
|
+
{
|
|
2851
|
+
"check": "validator-finding-absent",
|
|
2852
|
+
"finding_rule_id": "typescript.feed.room-post-fetched"
|
|
2853
|
+
}
|
|
2854
|
+
],
|
|
2855
|
+
"attestation": {
|
|
2856
|
+
"allowed": true,
|
|
2857
|
+
"host_agent_min_confidence": "high",
|
|
2858
|
+
"human_allowed": true,
|
|
2859
|
+
"evidence_required": [
|
|
2860
|
+
{
|
|
2861
|
+
"field": "room_display_name_source",
|
|
2862
|
+
"description": "How the room name is obtained and displayed in post cards.",
|
|
2863
|
+
"upload_policy": "upload-with-consent"
|
|
2864
|
+
}
|
|
2865
|
+
]
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
},
|
|
2869
|
+
{
|
|
2870
|
+
"id": "react-native.feed.poll-answer-data-shape",
|
|
2871
|
+
"version": 1,
|
|
2872
|
+
"title": "React Native poll answer text must be read from answer.data directly (plain string)",
|
|
2873
|
+
"severity": "warning",
|
|
2874
|
+
"rationale": "Amity.PollAnswer.data is a plain string containing the answer text, not an object. Accessing .data.text or casting to {text?: string} returns undefined, silently breaking poll rendering.",
|
|
2875
|
+
"applies_when": {
|
|
2876
|
+
"platforms": ["react-native"],
|
|
2877
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
2878
|
+
},
|
|
2879
|
+
"enforcement": {
|
|
2880
|
+
"deterministic": [
|
|
2881
|
+
{
|
|
2882
|
+
"check": "validator-finding-absent",
|
|
2883
|
+
"finding_rule_id": "react-native.feed.poll-answer-data-shape"
|
|
2884
|
+
}
|
|
2885
|
+
],
|
|
2886
|
+
"attestation": {
|
|
2887
|
+
"allowed": true,
|
|
2888
|
+
"host_agent_min_confidence": "high",
|
|
2889
|
+
"human_allowed": true,
|
|
2890
|
+
"evidence_required": [
|
|
2891
|
+
{
|
|
2892
|
+
"field": "poll_answer_rendering",
|
|
2893
|
+
"description": "How poll answer text is read from the SDK answer object and rendered.",
|
|
2894
|
+
"upload_policy": "upload-with-consent"
|
|
2895
|
+
}
|
|
2896
|
+
]
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
},
|
|
2900
|
+
{
|
|
2901
|
+
"id": "react-native.comments.query-has-limit",
|
|
2902
|
+
"version": 1,
|
|
2903
|
+
"title": "React Native comment query must include an explicit limit parameter",
|
|
2904
|
+
"severity": "warning",
|
|
2905
|
+
"rationale": "CommentRepository.getComments without an explicit limit triggers the SDK's default pagination which uses skip/limit — incompatible with the server's scrollable query type. Runtime error 500000: 'skip, limit and sorting are not supported in query type scrollable'.",
|
|
2906
|
+
"applies_when": {
|
|
2907
|
+
"platforms": ["react-native"],
|
|
2908
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
2909
|
+
},
|
|
2910
|
+
"enforcement": {
|
|
2911
|
+
"deterministic": [
|
|
2912
|
+
{
|
|
2913
|
+
"check": "validator-finding-absent",
|
|
2914
|
+
"finding_rule_id": "react-native.comments.query-has-limit"
|
|
2915
|
+
}
|
|
2916
|
+
],
|
|
2917
|
+
"attestation": {
|
|
2918
|
+
"allowed": true,
|
|
2919
|
+
"host_agent_min_confidence": "high",
|
|
2920
|
+
"human_allowed": true,
|
|
2921
|
+
"evidence_required": [
|
|
2922
|
+
{
|
|
2923
|
+
"field": "comment_query_params",
|
|
2924
|
+
"description": "The parameters passed to the comment live collection query, including limit/pageSize.",
|
|
2925
|
+
"upload_policy": "upload-with-consent"
|
|
2926
|
+
}
|
|
2927
|
+
]
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
},
|
|
2931
|
+
{
|
|
2932
|
+
"id": "react-native.community.display-name-from-sdk",
|
|
2933
|
+
"version": 1,
|
|
2934
|
+
"title": "React Native community display name must come from the SDK, not from a raw ID",
|
|
2935
|
+
"severity": "warning",
|
|
2936
|
+
"rationale": "Using a raw communityId as a displayName fallback (e.g. when passing only the ID in navigation state) shows a UUID to users instead of the community's actual name.",
|
|
2937
|
+
"applies_when": {
|
|
2938
|
+
"platforms": ["react-native"],
|
|
2939
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
2940
|
+
},
|
|
2941
|
+
"enforcement": {
|
|
2942
|
+
"deterministic": [
|
|
2943
|
+
{
|
|
2944
|
+
"check": "validator-finding-absent",
|
|
2945
|
+
"finding_rule_id": "react-native.community.display-name-from-sdk"
|
|
2946
|
+
}
|
|
2947
|
+
],
|
|
2948
|
+
"attestation": {
|
|
2949
|
+
"allowed": true,
|
|
2950
|
+
"host_agent_min_confidence": "high",
|
|
2951
|
+
"human_allowed": true,
|
|
2952
|
+
"evidence_required": [
|
|
2953
|
+
{
|
|
2954
|
+
"field": "community_name_source",
|
|
2955
|
+
"description": "Where the community display name is sourced from when rendering community references.",
|
|
2956
|
+
"upload_policy": "upload-with-consent"
|
|
2957
|
+
}
|
|
2958
|
+
]
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
},
|
|
2962
|
+
{
|
|
2963
|
+
"id": "react-native.feed.room-post-fetched",
|
|
2964
|
+
"version": 1,
|
|
2965
|
+
"title": "React Native room post must fetch room title via RoomRepository, not display raw roomId",
|
|
2966
|
+
"severity": "warning",
|
|
2967
|
+
"rationale": "Room is a first-class SDK entity with a title field. Displaying room.roomId as the room name shows a UUID. Use RoomRepository.getRoom to get room.title and room.status.",
|
|
2968
|
+
"applies_when": {
|
|
2969
|
+
"platforms": ["react-native"],
|
|
2970
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
2971
|
+
},
|
|
2972
|
+
"enforcement": {
|
|
2973
|
+
"deterministic": [
|
|
2974
|
+
{
|
|
2975
|
+
"check": "validator-finding-absent",
|
|
2976
|
+
"finding_rule_id": "react-native.feed.room-post-fetched"
|
|
2977
|
+
}
|
|
2978
|
+
],
|
|
2979
|
+
"attestation": {
|
|
2980
|
+
"allowed": true,
|
|
2981
|
+
"host_agent_min_confidence": "high",
|
|
2982
|
+
"human_allowed": true,
|
|
2983
|
+
"evidence_required": [
|
|
2984
|
+
{
|
|
2985
|
+
"field": "room_display_name_source",
|
|
2986
|
+
"description": "How the room name is obtained and displayed in post cards.",
|
|
2987
|
+
"upload_policy": "upload-with-consent"
|
|
2988
|
+
}
|
|
2989
|
+
]
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
},
|
|
2993
|
+
{
|
|
2994
|
+
"id": "flutter.feed.poll-answer-data-shape",
|
|
2995
|
+
"version": 1,
|
|
2996
|
+
"title": "Flutter poll answer text must be read from answer.data directly (plain string)",
|
|
2997
|
+
"severity": "warning",
|
|
2998
|
+
"rationale": "Amity.PollAnswer.data is a plain string containing the answer text, not an object. Accessing .data.text or casting to {text?: string} returns undefined, silently breaking poll rendering.",
|
|
2999
|
+
"applies_when": {
|
|
3000
|
+
"platforms": ["flutter"],
|
|
3001
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
3002
|
+
},
|
|
3003
|
+
"enforcement": {
|
|
3004
|
+
"deterministic": [
|
|
3005
|
+
{
|
|
3006
|
+
"check": "validator-finding-absent",
|
|
3007
|
+
"finding_rule_id": "flutter.feed.poll-answer-data-shape"
|
|
3008
|
+
}
|
|
3009
|
+
],
|
|
3010
|
+
"attestation": {
|
|
3011
|
+
"allowed": true,
|
|
3012
|
+
"host_agent_min_confidence": "high",
|
|
3013
|
+
"human_allowed": true,
|
|
3014
|
+
"evidence_required": [
|
|
3015
|
+
{
|
|
3016
|
+
"field": "poll_answer_rendering",
|
|
3017
|
+
"description": "How poll answer text is read from the SDK answer object and rendered.",
|
|
3018
|
+
"upload_policy": "upload-with-consent"
|
|
3019
|
+
}
|
|
3020
|
+
]
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
},
|
|
3024
|
+
{
|
|
3025
|
+
"id": "flutter.comments.query-has-limit",
|
|
3026
|
+
"version": 1,
|
|
3027
|
+
"title": "Flutter comment query must include an explicit limit parameter",
|
|
3028
|
+
"severity": "warning",
|
|
3029
|
+
"rationale": "CommentRepository.getComments without an explicit limit triggers the SDK's default pagination which uses skip/limit — incompatible with the server's scrollable query type. Runtime error 500000: 'skip, limit and sorting are not supported in query type scrollable'.",
|
|
3030
|
+
"applies_when": {
|
|
3031
|
+
"platforms": ["flutter"],
|
|
3032
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
3033
|
+
},
|
|
3034
|
+
"enforcement": {
|
|
3035
|
+
"deterministic": [
|
|
3036
|
+
{
|
|
3037
|
+
"check": "validator-finding-absent",
|
|
3038
|
+
"finding_rule_id": "flutter.comments.query-has-limit"
|
|
3039
|
+
}
|
|
3040
|
+
],
|
|
3041
|
+
"attestation": {
|
|
3042
|
+
"allowed": true,
|
|
3043
|
+
"host_agent_min_confidence": "high",
|
|
3044
|
+
"human_allowed": true,
|
|
3045
|
+
"evidence_required": [
|
|
3046
|
+
{
|
|
3047
|
+
"field": "comment_query_params",
|
|
3048
|
+
"description": "The parameters passed to the comment live collection query, including limit/pageSize.",
|
|
3049
|
+
"upload_policy": "upload-with-consent"
|
|
3050
|
+
}
|
|
3051
|
+
]
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
},
|
|
3055
|
+
{
|
|
3056
|
+
"id": "flutter.community.display-name-from-sdk",
|
|
3057
|
+
"version": 1,
|
|
3058
|
+
"title": "Flutter community display name must come from the SDK, not from a raw ID",
|
|
3059
|
+
"severity": "warning",
|
|
3060
|
+
"rationale": "Using a raw communityId as a displayName fallback (e.g. when passing only the ID in navigation state) shows a UUID to users instead of the community's actual name.",
|
|
3061
|
+
"applies_when": {
|
|
3062
|
+
"platforms": ["flutter"],
|
|
3063
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
3064
|
+
},
|
|
3065
|
+
"enforcement": {
|
|
3066
|
+
"deterministic": [
|
|
3067
|
+
{
|
|
3068
|
+
"check": "validator-finding-absent",
|
|
3069
|
+
"finding_rule_id": "flutter.community.display-name-from-sdk"
|
|
3070
|
+
}
|
|
3071
|
+
],
|
|
3072
|
+
"attestation": {
|
|
3073
|
+
"allowed": true,
|
|
3074
|
+
"host_agent_min_confidence": "high",
|
|
3075
|
+
"human_allowed": true,
|
|
3076
|
+
"evidence_required": [
|
|
3077
|
+
{
|
|
3078
|
+
"field": "community_name_source",
|
|
3079
|
+
"description": "Where the community display name is sourced from when rendering community references.",
|
|
3080
|
+
"upload_policy": "upload-with-consent"
|
|
3081
|
+
}
|
|
3082
|
+
]
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
},
|
|
3086
|
+
{
|
|
3087
|
+
"id": "flutter.feed.room-post-fetched",
|
|
3088
|
+
"version": 1,
|
|
3089
|
+
"title": "Flutter room post must fetch room title via RoomRepository, not display raw roomId",
|
|
3090
|
+
"severity": "warning",
|
|
3091
|
+
"rationale": "Room is a first-class SDK entity with a title field. Displaying room.roomId as the room name shows a UUID. Use RoomRepository.getRoom to get room.title and room.status.",
|
|
3092
|
+
"applies_when": {
|
|
3093
|
+
"platforms": ["flutter"],
|
|
3094
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
3095
|
+
},
|
|
3096
|
+
"enforcement": {
|
|
3097
|
+
"deterministic": [
|
|
3098
|
+
{
|
|
3099
|
+
"check": "validator-finding-absent",
|
|
3100
|
+
"finding_rule_id": "flutter.feed.room-post-fetched"
|
|
3101
|
+
}
|
|
3102
|
+
],
|
|
3103
|
+
"attestation": {
|
|
3104
|
+
"allowed": true,
|
|
3105
|
+
"host_agent_min_confidence": "high",
|
|
3106
|
+
"human_allowed": true,
|
|
3107
|
+
"evidence_required": [
|
|
3108
|
+
{
|
|
3109
|
+
"field": "room_display_name_source",
|
|
3110
|
+
"description": "How the room name is obtained and displayed in post cards.",
|
|
3111
|
+
"upload_policy": "upload-with-consent"
|
|
3112
|
+
}
|
|
3113
|
+
]
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
},
|
|
3117
|
+
{
|
|
3118
|
+
"id": "ios.comments.query-has-limit",
|
|
3119
|
+
"version": 1,
|
|
3120
|
+
"title": "iOS comment query must include an explicit pageSize parameter",
|
|
3121
|
+
"severity": "warning",
|
|
3122
|
+
"rationale": "CommentRepository.getComments without an explicit limit triggers the SDK's default pagination which uses skip/limit — incompatible with the server's scrollable query type. Runtime error 500000: 'skip, limit and sorting are not supported in query type scrollable'.",
|
|
3123
|
+
"applies_when": {
|
|
3124
|
+
"platforms": ["ios"],
|
|
3125
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
3126
|
+
},
|
|
3127
|
+
"enforcement": {
|
|
3128
|
+
"deterministic": [
|
|
3129
|
+
{
|
|
3130
|
+
"check": "validator-finding-absent",
|
|
3131
|
+
"finding_rule_id": "ios.comments.query-has-limit"
|
|
3132
|
+
}
|
|
3133
|
+
],
|
|
3134
|
+
"attestation": {
|
|
3135
|
+
"allowed": true,
|
|
3136
|
+
"host_agent_min_confidence": "high",
|
|
3137
|
+
"human_allowed": true,
|
|
3138
|
+
"evidence_required": [
|
|
3139
|
+
{
|
|
3140
|
+
"field": "comment_query_params",
|
|
3141
|
+
"description": "The parameters passed to the comment live collection query, including limit/pageSize.",
|
|
3142
|
+
"upload_policy": "upload-with-consent"
|
|
3143
|
+
}
|
|
3144
|
+
]
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
},
|
|
3148
|
+
{
|
|
3149
|
+
"id": "ios.community.display-name-from-sdk",
|
|
3150
|
+
"version": 1,
|
|
3151
|
+
"title": "iOS community display name must come from the SDK, not from a raw ID",
|
|
3152
|
+
"severity": "warning",
|
|
3153
|
+
"rationale": "Using a raw communityId as a displayName fallback (e.g. when passing only the ID in navigation state) shows a UUID to users instead of the community's actual name.",
|
|
3154
|
+
"applies_when": {
|
|
3155
|
+
"platforms": ["ios"],
|
|
3156
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
3157
|
+
},
|
|
3158
|
+
"enforcement": {
|
|
3159
|
+
"deterministic": [
|
|
3160
|
+
{
|
|
3161
|
+
"check": "validator-finding-absent",
|
|
3162
|
+
"finding_rule_id": "ios.community.display-name-from-sdk"
|
|
3163
|
+
}
|
|
3164
|
+
],
|
|
3165
|
+
"attestation": {
|
|
3166
|
+
"allowed": true,
|
|
3167
|
+
"host_agent_min_confidence": "high",
|
|
3168
|
+
"human_allowed": true,
|
|
3169
|
+
"evidence_required": [
|
|
3170
|
+
{
|
|
3171
|
+
"field": "community_name_source",
|
|
3172
|
+
"description": "Where the community display name is sourced from when rendering community references.",
|
|
3173
|
+
"upload_policy": "upload-with-consent"
|
|
3174
|
+
}
|
|
3175
|
+
]
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
},
|
|
3179
|
+
{
|
|
3180
|
+
"id": "ios.feed.room-post-fetched",
|
|
3181
|
+
"version": 1,
|
|
3182
|
+
"title": "iOS room post must fetch room title via RoomRepository, not display raw roomId",
|
|
3183
|
+
"severity": "warning",
|
|
3184
|
+
"rationale": "Room is a first-class SDK entity with a title field. Displaying room.roomId as the room name shows a UUID. Use RoomRepository.getRoom to get room.title and room.status.",
|
|
3185
|
+
"applies_when": {
|
|
3186
|
+
"platforms": ["ios"],
|
|
3187
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
3188
|
+
},
|
|
3189
|
+
"enforcement": {
|
|
3190
|
+
"deterministic": [
|
|
3191
|
+
{
|
|
3192
|
+
"check": "validator-finding-absent",
|
|
3193
|
+
"finding_rule_id": "ios.feed.room-post-fetched"
|
|
3194
|
+
}
|
|
3195
|
+
],
|
|
3196
|
+
"attestation": {
|
|
3197
|
+
"allowed": true,
|
|
3198
|
+
"host_agent_min_confidence": "high",
|
|
3199
|
+
"human_allowed": true,
|
|
3200
|
+
"evidence_required": [
|
|
3201
|
+
{
|
|
3202
|
+
"field": "room_display_name_source",
|
|
3203
|
+
"description": "How the room name is obtained and displayed in post cards.",
|
|
3204
|
+
"upload_policy": "upload-with-consent"
|
|
3205
|
+
}
|
|
3206
|
+
]
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
},
|
|
3210
|
+
{
|
|
3211
|
+
"id": "android.feed.poll-answer-data-shape",
|
|
3212
|
+
"version": 1,
|
|
3213
|
+
"title": "Android poll answer text must be read from answer.getData() directly (plain string)",
|
|
3214
|
+
"severity": "warning",
|
|
3215
|
+
"rationale": "Amity.PollAnswer.data is a plain string containing the answer text, not an object. Accessing .data.text or calling getText() as a chained object method returns undefined, silently breaking poll rendering.",
|
|
3216
|
+
"applies_when": {
|
|
3217
|
+
"platforms": ["android"],
|
|
3218
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
3219
|
+
},
|
|
3220
|
+
"enforcement": {
|
|
3221
|
+
"deterministic": [
|
|
3222
|
+
{
|
|
3223
|
+
"check": "validator-finding-absent",
|
|
3224
|
+
"finding_rule_id": "android.feed.poll-answer-data-shape"
|
|
3225
|
+
}
|
|
3226
|
+
],
|
|
3227
|
+
"attestation": {
|
|
3228
|
+
"allowed": true,
|
|
3229
|
+
"host_agent_min_confidence": "high",
|
|
3230
|
+
"human_allowed": true,
|
|
3231
|
+
"evidence_required": [
|
|
3232
|
+
{
|
|
3233
|
+
"field": "poll_answer_rendering",
|
|
3234
|
+
"description": "How poll answer text is read from the SDK answer object and rendered.",
|
|
3235
|
+
"upload_policy": "upload-with-consent"
|
|
3236
|
+
}
|
|
3237
|
+
]
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
},
|
|
3241
|
+
{
|
|
3242
|
+
"id": "android.comments.query-has-limit",
|
|
3243
|
+
"version": 1,
|
|
3244
|
+
"title": "Android comment query must include an explicit pageSize parameter",
|
|
3245
|
+
"severity": "warning",
|
|
3246
|
+
"rationale": "CommentRepository.getComments without an explicit limit triggers the SDK's default pagination which uses skip/limit — incompatible with the server's scrollable query type. Runtime error 500000: 'skip, limit and sorting are not supported in query type scrollable'.",
|
|
3247
|
+
"applies_when": {
|
|
3248
|
+
"platforms": ["android"],
|
|
3249
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
3250
|
+
},
|
|
3251
|
+
"enforcement": {
|
|
3252
|
+
"deterministic": [
|
|
3253
|
+
{
|
|
3254
|
+
"check": "validator-finding-absent",
|
|
3255
|
+
"finding_rule_id": "android.comments.query-has-limit"
|
|
3256
|
+
}
|
|
3257
|
+
],
|
|
3258
|
+
"attestation": {
|
|
3259
|
+
"allowed": true,
|
|
3260
|
+
"host_agent_min_confidence": "high",
|
|
3261
|
+
"human_allowed": true,
|
|
3262
|
+
"evidence_required": [
|
|
3263
|
+
{
|
|
3264
|
+
"field": "comment_query_params",
|
|
3265
|
+
"description": "The parameters passed to the comment live collection query, including limit/pageSize.",
|
|
3266
|
+
"upload_policy": "upload-with-consent"
|
|
3267
|
+
}
|
|
3268
|
+
]
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
},
|
|
3272
|
+
{
|
|
3273
|
+
"id": "android.community.display-name-from-sdk",
|
|
3274
|
+
"version": 1,
|
|
3275
|
+
"title": "Android community display name must come from the SDK, not from a raw ID",
|
|
3276
|
+
"severity": "warning",
|
|
3277
|
+
"rationale": "Using a raw communityId as a displayName fallback (e.g. when passing only the ID in navigation state) shows a UUID to users instead of the community's actual name.",
|
|
3278
|
+
"applies_when": {
|
|
3279
|
+
"platforms": ["android"],
|
|
3280
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
3281
|
+
},
|
|
3282
|
+
"enforcement": {
|
|
3283
|
+
"deterministic": [
|
|
3284
|
+
{
|
|
3285
|
+
"check": "validator-finding-absent",
|
|
3286
|
+
"finding_rule_id": "android.community.display-name-from-sdk"
|
|
3287
|
+
}
|
|
3288
|
+
],
|
|
3289
|
+
"attestation": {
|
|
3290
|
+
"allowed": true,
|
|
3291
|
+
"host_agent_min_confidence": "high",
|
|
3292
|
+
"human_allowed": true,
|
|
3293
|
+
"evidence_required": [
|
|
3294
|
+
{
|
|
3295
|
+
"field": "community_name_source",
|
|
3296
|
+
"description": "Where the community display name is sourced from when rendering community references.",
|
|
3297
|
+
"upload_policy": "upload-with-consent"
|
|
3298
|
+
}
|
|
3299
|
+
]
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
},
|
|
3303
|
+
{
|
|
3304
|
+
"id": "android.feed.room-post-fetched",
|
|
3305
|
+
"version": 1,
|
|
3306
|
+
"title": "Android room post must fetch room title via RoomRepository, not display raw roomId",
|
|
3307
|
+
"severity": "warning",
|
|
3308
|
+
"rationale": "Room is a first-class SDK entity with a title field. Displaying room.roomId as the room name shows a UUID. Use RoomRepository.getRoom to get room.title and room.status.",
|
|
3309
|
+
"applies_when": {
|
|
3310
|
+
"platforms": ["android"],
|
|
3311
|
+
"outcomes": ["add-feed", "add-comments", "validate-setup"]
|
|
3312
|
+
},
|
|
3313
|
+
"enforcement": {
|
|
3314
|
+
"deterministic": [
|
|
3315
|
+
{
|
|
3316
|
+
"check": "validator-finding-absent",
|
|
3317
|
+
"finding_rule_id": "android.feed.room-post-fetched"
|
|
3318
|
+
}
|
|
3319
|
+
],
|
|
3320
|
+
"attestation": {
|
|
3321
|
+
"allowed": true,
|
|
3322
|
+
"host_agent_min_confidence": "high",
|
|
3323
|
+
"human_allowed": true,
|
|
3324
|
+
"evidence_required": [
|
|
3325
|
+
{
|
|
3326
|
+
"field": "room_display_name_source",
|
|
3327
|
+
"description": "How the room name is obtained and displayed in post cards.",
|
|
3328
|
+
"upload_policy": "upload-with-consent"
|
|
3329
|
+
}
|
|
3330
|
+
]
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
},
|
|
3334
|
+
{
|
|
3335
|
+
"id": "typescript.comments.creation-affordance-present",
|
|
3336
|
+
"version": 1,
|
|
3337
|
+
"title": "TypeScript comment list must provide a creation affordance",
|
|
3338
|
+
"severity": "warning",
|
|
3339
|
+
"rationale": "A surface that reads comments (getComments) but never calls createComment leaves users with a read-only list and no way to participate. The comment composer may live in a sibling file, so detection is codebase-wide with an attestation escape for deliberately read-only views.",
|
|
3340
|
+
"applies_when": {
|
|
3341
|
+
"platforms": [
|
|
3342
|
+
"typescript"
|
|
3343
|
+
],
|
|
3344
|
+
"outcomes": [
|
|
3345
|
+
"add-feed",
|
|
3346
|
+
"add-comments",
|
|
3347
|
+
"validate-setup"
|
|
3348
|
+
]
|
|
3349
|
+
},
|
|
3350
|
+
"enforcement": {
|
|
3351
|
+
"deterministic": [
|
|
3352
|
+
{
|
|
3353
|
+
"check": "validator-finding-absent",
|
|
3354
|
+
"finding_rule_id": "typescript.comments.creation-affordance-present"
|
|
3355
|
+
}
|
|
3356
|
+
],
|
|
3357
|
+
"attestation": {
|
|
3358
|
+
"allowed": true,
|
|
3359
|
+
"host_agent_min_confidence": "high",
|
|
3360
|
+
"human_allowed": true,
|
|
3361
|
+
"evidence_required": [
|
|
3362
|
+
{
|
|
3363
|
+
"field": "comment_creation_path",
|
|
3364
|
+
"description": "Where and how comment creation (createComment) is wired, or why the comment surface is intentionally read-only.",
|
|
3365
|
+
"upload_policy": "upload-with-consent"
|
|
3366
|
+
}
|
|
3367
|
+
]
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
},
|
|
3371
|
+
{
|
|
3372
|
+
"id": "react-native.comments.creation-affordance-present",
|
|
3373
|
+
"version": 1,
|
|
3374
|
+
"title": "React Native comment list must provide a creation affordance",
|
|
3375
|
+
"severity": "warning",
|
|
3376
|
+
"rationale": "A surface that reads comments (getComments) but never calls createComment leaves users with a read-only list and no way to participate. The comment composer may live in a sibling file, so detection is codebase-wide with an attestation escape for deliberately read-only views.",
|
|
3377
|
+
"applies_when": {
|
|
3378
|
+
"platforms": [
|
|
3379
|
+
"react-native"
|
|
3380
|
+
],
|
|
3381
|
+
"outcomes": [
|
|
3382
|
+
"add-feed",
|
|
3383
|
+
"add-comments",
|
|
3384
|
+
"validate-setup"
|
|
3385
|
+
]
|
|
3386
|
+
},
|
|
3387
|
+
"enforcement": {
|
|
3388
|
+
"deterministic": [
|
|
3389
|
+
{
|
|
3390
|
+
"check": "validator-finding-absent",
|
|
3391
|
+
"finding_rule_id": "react-native.comments.creation-affordance-present"
|
|
3392
|
+
}
|
|
3393
|
+
],
|
|
3394
|
+
"attestation": {
|
|
3395
|
+
"allowed": true,
|
|
3396
|
+
"host_agent_min_confidence": "high",
|
|
3397
|
+
"human_allowed": true,
|
|
3398
|
+
"evidence_required": [
|
|
3399
|
+
{
|
|
3400
|
+
"field": "comment_creation_path",
|
|
3401
|
+
"description": "Where and how comment creation (createComment) is wired, or why the comment surface is intentionally read-only.",
|
|
3402
|
+
"upload_policy": "upload-with-consent"
|
|
3403
|
+
}
|
|
3404
|
+
]
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
},
|
|
3408
|
+
{
|
|
3409
|
+
"id": "flutter.comments.creation-affordance-present",
|
|
3410
|
+
"version": 1,
|
|
3411
|
+
"title": "Flutter comment list must provide a creation affordance",
|
|
3412
|
+
"severity": "warning",
|
|
3413
|
+
"rationale": "A surface that reads comments (getComments) but never calls createComment leaves users with a read-only list and no way to participate. The comment composer may live in a sibling file, so detection is codebase-wide with an attestation escape for deliberately read-only views.",
|
|
3414
|
+
"applies_when": {
|
|
3415
|
+
"platforms": [
|
|
3416
|
+
"flutter"
|
|
3417
|
+
],
|
|
3418
|
+
"outcomes": [
|
|
3419
|
+
"add-feed",
|
|
3420
|
+
"add-comments",
|
|
3421
|
+
"validate-setup"
|
|
3422
|
+
]
|
|
3423
|
+
},
|
|
3424
|
+
"enforcement": {
|
|
3425
|
+
"deterministic": [
|
|
3426
|
+
{
|
|
3427
|
+
"check": "validator-finding-absent",
|
|
3428
|
+
"finding_rule_id": "flutter.comments.creation-affordance-present"
|
|
3429
|
+
}
|
|
3430
|
+
],
|
|
3431
|
+
"attestation": {
|
|
3432
|
+
"allowed": true,
|
|
3433
|
+
"host_agent_min_confidence": "high",
|
|
3434
|
+
"human_allowed": true,
|
|
3435
|
+
"evidence_required": [
|
|
3436
|
+
{
|
|
3437
|
+
"field": "comment_creation_path",
|
|
3438
|
+
"description": "Where and how comment creation (createComment) is wired, or why the comment surface is intentionally read-only.",
|
|
3439
|
+
"upload_policy": "upload-with-consent"
|
|
3440
|
+
}
|
|
3441
|
+
]
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
},
|
|
3445
|
+
{
|
|
3446
|
+
"id": "ios.comments.creation-affordance-present",
|
|
3447
|
+
"version": 1,
|
|
3448
|
+
"title": "iOS comment list must provide a creation affordance",
|
|
3449
|
+
"severity": "warning",
|
|
3450
|
+
"rationale": "A surface that reads comments (getComments) but never calls createComment leaves users with a read-only list and no way to participate. The comment composer may live in a sibling file, so detection is codebase-wide with an attestation escape for deliberately read-only views.",
|
|
3451
|
+
"applies_when": {
|
|
3452
|
+
"platforms": [
|
|
3453
|
+
"ios"
|
|
3454
|
+
],
|
|
3455
|
+
"outcomes": [
|
|
3456
|
+
"add-feed",
|
|
3457
|
+
"add-comments",
|
|
3458
|
+
"validate-setup"
|
|
3459
|
+
]
|
|
3460
|
+
},
|
|
3461
|
+
"enforcement": {
|
|
3462
|
+
"deterministic": [
|
|
3463
|
+
{
|
|
3464
|
+
"check": "validator-finding-absent",
|
|
3465
|
+
"finding_rule_id": "ios.comments.creation-affordance-present"
|
|
3466
|
+
}
|
|
3467
|
+
],
|
|
3468
|
+
"attestation": {
|
|
3469
|
+
"allowed": true,
|
|
3470
|
+
"host_agent_min_confidence": "high",
|
|
3471
|
+
"human_allowed": true,
|
|
3472
|
+
"evidence_required": [
|
|
3473
|
+
{
|
|
3474
|
+
"field": "comment_creation_path",
|
|
3475
|
+
"description": "Where and how comment creation (createComment) is wired, or why the comment surface is intentionally read-only.",
|
|
3476
|
+
"upload_policy": "upload-with-consent"
|
|
3477
|
+
}
|
|
3478
|
+
]
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
},
|
|
3482
|
+
{
|
|
3483
|
+
"id": "android.comments.creation-affordance-present",
|
|
3484
|
+
"version": 1,
|
|
3485
|
+
"title": "Android comment list must provide a creation affordance",
|
|
3486
|
+
"severity": "warning",
|
|
3487
|
+
"rationale": "A surface that reads comments (getComments) but never calls createComment leaves users with a read-only list and no way to participate. The comment composer may live in a sibling file, so detection is codebase-wide with an attestation escape for deliberately read-only views.",
|
|
3488
|
+
"applies_when": {
|
|
3489
|
+
"platforms": [
|
|
3490
|
+
"android"
|
|
3491
|
+
],
|
|
3492
|
+
"outcomes": [
|
|
3493
|
+
"add-feed",
|
|
3494
|
+
"add-comments",
|
|
3495
|
+
"validate-setup"
|
|
3496
|
+
]
|
|
3497
|
+
},
|
|
3498
|
+
"enforcement": {
|
|
3499
|
+
"deterministic": [
|
|
3500
|
+
{
|
|
3501
|
+
"check": "validator-finding-absent",
|
|
3502
|
+
"finding_rule_id": "android.comments.creation-affordance-present"
|
|
3503
|
+
}
|
|
3504
|
+
],
|
|
3505
|
+
"attestation": {
|
|
3506
|
+
"allowed": true,
|
|
3507
|
+
"host_agent_min_confidence": "high",
|
|
3508
|
+
"human_allowed": true,
|
|
3509
|
+
"evidence_required": [
|
|
3510
|
+
{
|
|
3511
|
+
"field": "comment_creation_path",
|
|
3512
|
+
"description": "Where and how comment creation (createComment) is wired, or why the comment surface is intentionally read-only.",
|
|
3513
|
+
"upload_policy": "upload-with-consent"
|
|
3514
|
+
}
|
|
3515
|
+
]
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
2380
3519
|
]
|
|
2381
|
-
}
|
|
3520
|
+
}
|