Discourse ACL Authoring
Use this skill before adding or reviewing ACL-backed resource permissions in Discourse core or plugins.
Workflow
- Identify the target resource and its
ACL_PERMISSIONSdeclaration. Prefer simple IDs such asview,edit, andmanage, but confirm the target's domain semantics. - Read references/backend-model.md when touching
AccessControlList,AclTarget,Acl::Target,Acl::User,Guardian,User#permission_acl, or target visibility queries. - Read references/manager-and-plugin-integration.md when writing ACLs, creating target models, registering plugin target classes, serializing ACLs, or integrating services/controllers.
- Read references/frontend-d-access-control.md when using or customizing
DAccessControl,DAccessControlField, or FormKit ACL validation. - Read references/testing-and-review.md when adding specs or reviewing an ACL feature.
- Also load
.skills/discourse-service-authoringforService::Basechanges,.skills/discourse-writing-rspec-testsfor RSpec, and.skills/discourse-writing-js-testsfor QUnit.
Non-Negotiables
- Define or inherit
ACL_PERMISSIONS = Acl::Permissions.new(...)on every ACL target. Use its named readers in permission declarations; the manager validates final grants against its values before replacing rows. - Every permission option added by
transformPermissionOptions(options)must have an ID matching the target's server-sideACL_PERMISSIONS. Adding a UI option does not declare a server permission. - Put authorization at the caller boundary before
AccessControlListManager.call; the manager is a destructive replacement service and currently assumes the caller already authorized the actor. - Always call
AccessControlListManagerfor writes, including empty submitted ACL arrays, so mandatory ACLs are injected and old rows are replaced intentionally. - Treat
AccessControlList.flattened_listas the API shape for UI/client payloads andAccessControlList.expand_list_for_bulk_insertas the DB insert shape. - Use Guardian ACL helpers or
AclTargetvisibility scopes for checks and target scopes instead of hand-querying ACL tables in controllers. - Register plugin ACL targets with
DiscoursePluginRegistry.register_acl_target_classsoSite#access_controlexposes mandatory and banned ACL metadata to the frontend. - Treat
banned_aclas a server-enforced restriction.DAccessControlhides banned permission choices for UX, butAccessControlListManagermust still reject submitted banned entries. - Define
loss_warning_permissionsas permission strings whose loss by the current actor requires confirmation. Provide the target-specific server translation used byAccessControlList::EvaluateModification. - Prefer
DAccessControlFieldinside FormKit. It owns required-permission validation, evaluates the proposed ACL, and uses FormKit'spreventSubmitto cancel a submission without manufacturing a validation error. - Keep the default class-name
acl_target_keywhen usingDAccessControlField; its@aclTarget.typecurrently serves as both the Ruby class identifier and frontend metadata key. - User grants are supported by backend storage/lookups and the frontend user/group search picker. Mandatory user ACL display still needs explicit UI support; the component's mandatory injection remains group-first.
Local Anchors
- Model and relation API:
app/models/access_control_list.rb - Target concern:
app/models/concerns/acl_target.rb - Permission declarations:
lib/acl/permissions.rb,spec/lib/acl/permissions_spec.rb - Permission lookup objects:
lib/acl/target.rb,lib/acl/user.rb - Guardian helpers:
lib/guardian.rb - User ACL cache:
app/models/user.rb - Write manager:
app/services/access_control_list_manager.rb - Modification evaluation:
app/services/access_control_list/evaluate_modification.rb,app/controllers/access_control_lists_controller.rb - Deleted grantee cleanup:
app/jobs/regular/cleanup_acls_for_deleted.rb,app/models/group.rb,app/models/user.rb - Site metadata:
app/models/site.rb,app/serializers/site_serializer.rb - Frontend components:
frontend/discourse/app/ui-kit/d-access-control.gjs,frontend/discourse/app/ui-kit/d-access-control-field.gjs - Core specs:
spec/models/access_control_list_spec.rb,spec/models/concerns/acl_target_spec.rb,spec/lib/acl/target_spec.rb,spec/jobs/regular/cleanup_acls_for_deleted_spec.rb,spec/services/access_control_list_manager_spec.rb,spec/services/access_control_list/evaluate_modification_spec.rb,spec/serializers/site_serializer_spec.rb,frontend/discourse/tests/integration/components/d-access-control-test.gjs,frontend/discourse/tests/integration/components/form-kit/field-test.gjs - Plugin consumer example:
plugins/boards, especiallyapp/models/boards/board.rband its create/update services and request specs.