# Discourse Acl Authoring

> Use when creating, editing, or reviewing Discourse access-control-list features built on AccessControlList, AclTarget, Guardian ACL helpers, AccessControlListManager, mandatory_acl, banned_acl, loss_warning_permissions, ACL modification evaluation, Site access_control metadata, plugin ACL target registration, DAccessControl, or DAccessControlField.

- Skill: `discourse/discourse-acl-authoring` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add discourse/discourse-acl-authoring`
- Raw SKILL.md: https://api.skillmd.com/api/skills/discourse/discourse-acl-authoring/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: discourse (https://skillmd.com/u/discourse)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/discourse/discourse-acl-authoring

---


# Discourse ACL Authoring

Use this skill before adding or reviewing ACL-backed resource permissions in Discourse core or plugins.

## Workflow

1. Identify the target resource and its `ACL_PERMISSIONS` declaration. Prefer simple IDs such as `view`, `edit`, and `manage`, but confirm the target's domain semantics.
2. Read [references/backend-model.md](references/backend-model.md) when touching `AccessControlList`, `AclTarget`, `Acl::Target`, `Acl::User`, `Guardian`, `User#permission_acl`, or target visibility queries.
3. Read [references/manager-and-plugin-integration.md](references/manager-and-plugin-integration.md) when writing ACLs, creating target models, registering plugin target classes, serializing ACLs, or integrating services/controllers.
4. Read [references/frontend-d-access-control.md](references/frontend-d-access-control.md) when using or customizing `DAccessControl`, `DAccessControlField`, or FormKit ACL validation.
5. Read [references/testing-and-review.md](references/testing-and-review.md) when adding specs or reviewing an ACL feature.
6. Also load `.skills/discourse-service-authoring` for `Service::Base` changes, `.skills/discourse-writing-rspec-tests` for RSpec, and `.skills/discourse-writing-js-tests` for 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-side `ACL_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 `AccessControlListManager` for writes, including empty submitted ACL arrays, so mandatory ACLs are injected and old rows are replaced intentionally.
- Treat `AccessControlList.flattened_list` as the API shape for UI/client payloads and `AccessControlList.expand_list_for_bulk_insert` as the DB insert shape.
- Use Guardian ACL helpers or `AclTarget` visibility scopes for checks and target scopes instead of hand-querying ACL tables in controllers.
- Register plugin ACL targets with `DiscoursePluginRegistry.register_acl_target_class` so `Site#access_control` exposes mandatory and banned ACL metadata to the frontend.
- Treat `banned_acl` as a server-enforced restriction. `DAccessControl` hides banned permission choices for UX, but `AccessControlListManager` must still reject submitted banned entries.
- Define `loss_warning_permissions` as permission strings whose loss by the current actor requires confirmation. Provide the target-specific server translation used by `AccessControlList::EvaluateModification`.
- Prefer `DAccessControlField` inside FormKit. It owns required-permission validation, evaluates the proposed ACL, and uses FormKit's `preventSubmit` to cancel a submission without manufacturing a validation error.
- Keep the default class-name `acl_target_key` when using `DAccessControlField`; its `@aclTarget.type` currently 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`, especially `app/models/boards/board.rb` and its create/update services and request specs.

