.skills/discourse-acl-authoring/references/testing-and-review.md
Use this reference when adding specs or reviewing ACL-backed features.
For target models:
AclTarget.mandatory_acl if the target defines one.loss_warning_permissions when the target requires confirmation before the current actor loses a permission.anonymous_can_read? or can_write? that wrap permission_acl..with_acl_permission and .with_any_acl_permissions when index/list actions depend on the shared scopes.For AccessControlList::EvaluateModification:
user_will_have_permission.loss_warning_permissions value fails user_will_not_lose_permission.For Guardian methods:
granular_anonymous_and_logged_in_groups_permissions behavior when relevant.For services using AccessControlListManager:
acl persists mandatory ACLs.acl: [] persists mandatory ACLs or fails closed when no mandatory ACL exists.acl: [] replaces existing ACLs with mandatory ACLs.banned_acl fail the has_no_banned_acl policy.permission_acl cache.For cleanup behavior:
:cleanup_acls_for_deleted.Jobs::CleanupAclsForDeleted removes the group id from ACL rows.:cleanup_acls_for_deleted.Jobs::CleanupAclsForDeleted removes the user id from ACL rows.For lookup objects:
Acl::Target#permission_group_ids returns [] for missing permissions and a defensive copy for present permissions.Acl::Target#group_ids_with_any_permission replaces the older multi_permission_group_ids name.Acl::Target#permission_user_ids and #user_ids_with_any_permission return [] for missing permissions and defensive array copies for present permissions.Acl::User#target_ids_with_permission and #target_ids_with_any_permissions return defensive array copies.For migration specs:
For DAccessControl consumers:
site.access_control.mandatory_acl[targetKey] appears and is locked.site.access_control.banned_acl[targetKey] removes matching permission options for the matching grantee.onChange writes updated ACL arrays into parent state.For DAccessControlField consumers:
@mustHavePermissions adds a visible field error when no ACL entry has an allowed permission.@mustHavePermissions permits removal of the final manager when that is the intended confirmed flow.@onSubmit without adding a visible error.Use .skills/discourse-writing-js-tests for QUnit patterns.
Ask these questions during code review:
AccessControlListManager?@aclTarget.type contain the registered Ruby class name, and does the target retain the default matching acl_target_key?loss_warning_permissions, does it provide the matching access_control_list.errors.<acl_target_key>_user_will_lose_permission server translation?DAccessControlField or deliberately document why its validation flow differs?Target.with_acl_permission, Target.with_any_acl_permissions, guardian.target_ids_with_acl_permission, or target_ids_with_any_acl_permissions instead of ad hoc SQL?DiscoursePluginRegistry.register_acl_target_class?anonymous_users, logged_in_users, or everyone can appear in ACLs?AccessControlListManager currently assumes caller-side authorization.allowed_user_ids, but shared UI authoring is not complete.DAccessControl remains group-first and does not provide complete user ACL editing yet.DAccessControl injects mandatory rows for display but does not notify the parent on render.DAccessControl filters banned permission options for UX, but hidden options are not authorization. The manager policy is the enforcement point.DAccessControlField cancellation leaves the proposed ACL in draft state. preventSubmit is not a rollback API.DAccessControlField uses @aclTarget.type for class lookup and frontend metadata lookup, while site metadata is keyed by acl_target_key. A custom key cannot currently satisfy both paths.mandatory_values.flattened_list skips stale group IDs, stale user IDs, and unknown target classes, so a missing row can disappear from serialized ACL output while cleanup or validation catches up.