symfony-authorizationlisted
Install: claude install-skill ubermuda/loupe
# Symfony Authorization (Voters + `#[IsGranted]`)
Voter classes hold the policy. Controllers and templates express intent through action-semantic attributes. Project hard rules sit on top of this layer in `project-authz`. Read both skills when you touch access control in this repo.
## Attributes are actions, not properties
An attribute names what the caller tries to do. It never names a property of the user: write `'project.create'`, not `IS_ORG_OWNER`. The policy ("only the org owner may do this") lives inside the voter, and can change without touching a call site.
Name every attribute `{resource}.{action}` in lower-case, dot-separated form. Write the resource noun singular, then the action verb: `'project.create'`, `'project.manage'`, `'issue.view'`, `'workspace.manage'`, `'organization.configure'`. Mirror it in the PHP constant as `{RESOURCE}_{ACTION}`: `PROJECT_CREATE`, `ISSUE_VIEW`, `WORKSPACE_MANAGE`. The action half is a verb for what the caller does: `create`, `list`, `view`, `manage`, `configure`.
- Never use kebab-case (`'create-project'`).
- Never put the verb first (`CREATE_PROJECT`).
- Never use a plural (`'manage-workspaces'`).
Separate READ from WRITE actions per resource. Define separate constants even when today's policy grants both to the same role. Use `ISSUE_VIEW` for read-only GET endpoints, and `ISSUE_MANAGE` for state-changing POST/PUT/DELETE endpoints.
A write permission on a read endpoint is harmless. A read permission on a write endpoint is a