nocobase-acl-manage
Installation
SKILL.md
Goal
Turn ACL and permission governance into a task-driven workflow so users can ask for business outcomes while the skill handles:
- CLI command selection and argument shaping
- capability checks and safety guards
- write confirmation and readback evidence
- risk-oriented explanation for high-impact changes
Scope
- This skill is CLI-first for reads and writes.
- Tasks are grouped into five domains:
- role
- global role mode
- permission
- user
- risk assessment
- Every write task follows
plan -> confirm -> apply -> readback. global role modeis treated as a global system policy, not a per-role field.
Non-Goals
- Do not bypass CLI by calling direct REST endpoints.
- Do not mutate ACL through ad-hoc database operations.
- Do not create temporary script files to execute ACL writes.
- Do not invoke other skills for env/plugin bootstrap; use direct
nbcommands in this skill. - Do not hide high-impact blast radius (global mode, broad snippets, broad strategy actions).
- Do not claim one-click coverage for workflows that require governance review.
Canonical Task Model
A) Role Domain
| Task | User Outcome | Required Inputs | Optional Inputs |
|---|---|---|---|
role.audit-all |
list all roles with comparable policy summary | none | data_source_key, output |
role.create-blank |
create a role with default read-only baseline (single creation mode) | role_name |
role_title, description, hidden, allow_configure, allow_new_menu |
role.compare |
explain differences between roles | role_names[] |
data_source_key, output |
B) Global Role-Mode Domain
| Task | User Outcome | Required Inputs | Optional Inputs |
|---|---|---|---|
global.role-mode.get |
read current global role mode | none | output |
global.role-mode.set |
switch global role mode | role_mode |
strict_mode |
role_mode enum mapping:
default: independent role usage (no union mode)allow-use-union: union mode available, role switching still allowedonly-use-union: force union mode for multi-role users
C) Permission Domain
| Task | User Outcome | Required Inputs | Optional Inputs |
|---|---|---|---|
permission.system-snippets.set |
set role-level system snippets | role_name, (snippet_preset or snippets) |
none |
permission.route.desktop.set |
set desktop route permissions for a role | role_name, route_ids[] |
set_mode (set or add or remove) |
permission.data-source.global.set |
set global strategy actions for all tables in one data source | role_name, global_actions[] |
data_source_key |
permission.data-source.resource.set |
set independent actions for one or more collections | role_name, (collection_hint or collection_hints[]), actions[] |
data_source_key, fields_map, scope_map, resource_scope (all by default) |
permission.scope.manage |
create/update/list reusable scopes | scope_task |
data_source_key, scope_id, scope_payload |
D) User Domain
| Task | User Outcome | Required Inputs | Optional Inputs |
|---|---|---|---|
user.assign-role |
assign one role to one or many users | role_name, (user_ids[] or user_filter) |
allow_generic_association_write |
user.unassign-role |
remove one role from one or many users | role_name, (user_ids[] or user_filter) |
allow_generic_association_write |
user.audit-role-membership |
inspect users bound to roles | (role_name or user_id) |
output |
E) Risk Domain
| Task | User Outcome | Required Inputs | Optional Inputs |
|---|---|---|---|
risk.assess-role |
risk score and rationale for one role | role_name |
data_source_key, output |
risk.assess-user |
risk score based on user-role-permission relationship | user_id |
data_source_key, output |
risk.assess-system |
system-level ACL governance risk summary | none | data_source_key, output |
Role creation interaction policy:
- always create with the same default read-only baseline (
role.create-blank) role_namemust use NocoBase role uid format withr_prefix- if user input has no
r_prefix, normalize tor_<normalized_name>and show normalized value in confirmation/readback - do not ask users to choose role archetypes (for example, "employee/auditor/manager/custom")
- if
role_nameis provided, execute creation directly - after creation succeeds, move to permission assignment guidance
- permission follow-up options: system snippets, desktop routes, data-source global strategy, data-source resource strategy
Resource permission interaction policy:
- before executing
permission.data-source.resource.set, always confirm: - data source key (
mainby default unless user specifies another) - resolved target collection(s)
- action list
- data scope (
allby default when user does not specify) - disambiguate operation verbs from ACL action names:
- phrases like
add table permission/configure permissiondescribe the operation, not ACL actioncreate - only treat
createas selected action when user clearly asks for data-creation capability (can create records/can add data) - user does not need to provide exact technical collection names
- accept business names or keywords as
collection_hint(s) - resolve real collection names from the selected data source collection list
- if matching is ambiguous, present candidates and ask user to choose
- if no match is found, ask user for a clearer business keyword
- resolve scope binding before write:
- if user does not specify scope, default to
all all-> built-in scopekey=allid in target data sourceown-> built-in scopekey=ownid in target data sourcecustom-> user-specified scope id (or resolved scope key)- do not leave action scope as
nullwhen final scope isallorown - write completeness (hard rule):
- for
permission.data-source.resource.set, execute one complete write payload per target collection - the write payload must include
usingActionsConfig: true - the same payload must include the final
actions[]set, with explicit scope binding (scopeIdorscopeKey) forall|ownand explicit non-emptyfieldsarrays for field-configurable actions - do not stage writes as "set actions first, then patch fields/scope/usingActionsConfig"
- before any write, show confirmation summary (data source + resolved collections + actions + scope)
- when scope is defaulted, confirmation must explicitly state
scope=all (default)and allow user override - if any required item is missing or unresolved, ask user first and do not write
- default field rule: all fields
- full-field default must be written as explicit field-name lists resolved from target collection metadata
- do not use
fields=[]as a full-field default marker - if user did not provide field-level restrictions, apply full-field permission for each selected action
- apply full-field default to every selected action that supports field configuration (
create,view,update,export,importXlsx) - full-field default must include system fields returned by metadata (for example
sort,createdBy,createdById,updatedBy,updatedById) unless user explicitly asks to exclude them - use technical field names (
field.name) for writes, never display titles - do not auto-drop fields only because they are system/context/relation/hidden; only exclude when user intent explicitly restricts them
viewaction must default to all fields for that collection- if user explicitly asks for
all permissionson a collection, resolve runtime available actions and confirm expanded action set before write
Input Contract
| Input | Required | Default | Validation | Clarification Question |
|---|---|---|---|---|
task |
yes | none | one of canonical tasks or alias | "Which ACL governance task should run?" |
role_name |
conditional | none | role exists for update/audit tasks; write tasks normalize to r_* uid |
"Which role should be targeted?" |
role_mode |
conditional | none | one of default/allow-use-union/only-use-union |
"Which global role mode should be set?" |
collection_hint / collection_hints[] |
conditional | none | required for permission.data-source.resource.set; business name/keyword input is allowed |
"Which business table(s) should be configured?" |
resolved_collection_names[] |
conditional | runtime resolved | required before write for permission.data-source.resource.set; each collection must exist in selected data source |
"I found these matching collections. Which should be used?" |
actions[] |
conditional | none | required for permission.data-source.resource.set |
"Which actions should be granted on these collections?" |
resource_scope |
no | all |
one of all / own / custom(scope_id or scope_filter) |
"Which data scope should be used? Default is all; choose own/custom only when needed." |
data_source_key |
no | main |
must exist at runtime | "Which data source key should be used? (default: main)" |
strict_mode |
no | safe |
safe or fast |
"Use safe mode with full readback?" |
allow_generic_association_write |
no | false |
boolean | "Allow guarded generic association writes for user-role assignment?" |
output |
no | text+matrix |
text, text+matrix, text+evidence |
"How detailed should the result be?" |
Default behavior when user says you decide:
- choose role tasks for role intent
- choose permission tasks for policy intent
- choose user tasks for assignment intent
- choose risk tasks for assessment intent
- for role creation intent, always use
role.create-blankfirst data_source_key=mainstrict_mode=safe
Mandatory Clarification Gate
- max clarification rounds:
2 - max questions per round:
3 - never execute writes before required inputs are complete
- for
role.create-blank, ask only forrole_namewhen missing; do not ask role-type questions - for write tasks with
role_name, normalize tor_*uid and echo normalized value in confirmation - for
permission.data-source.resource.set, defaultdata_source_key=mainwhen omitted unless user explicitly provides another data source - for
permission.data-source.resource.set, if collection hint cannot be resolved or is ambiguous, ask follow-up questions before write - for
permission.data-source.resource.set, if actions are incomplete, ask follow-up questions before write - for
permission.data-source.resource.set, if scope is omitted, apply defaultalland require confirmation before write - for
permission.data-source.resource.set, if custom scope is requested but scope id/key is unresolved, ask follow-up questions before write - for
permission.data-source.resource.set, if user has not confirmed the final write plan, do not write - if user asks to set role mode for a specific role, clarify and normalize to global mode change
- if task implies writes and target identity is missing, stop and ask first
- if CLI returns auth errors (
401,403,Auth required), stop and request recovery (@nocobase/plugin-api-keysactivation + token refresh)
Workflow
- Resolve intent and normalize task.
- map aliases to canonical tasks
- map natural-language role mode wording to
default/allow-use-union/only-use-union - normalize create-role wording to
role.create-blankbaseline first, then permission assignment
- Capability gate (CLI).
- confirm direct
nbCLI is available in PATH - command assembly guard:
- command form must be
nb <command> [subcommand ...] [flags ...] - first token after
nbmust be a command (for exampleenvorapi), not a flag such as-e/-t/-j - wrong:
nb -e local - correct:
nb api resource list --resource users -e local -j - raw JSON input guard (PowerShell/runtime):
- prefer structured body flags (for example
--resources,--actions) over inline--bodywhen possible - if raw JSON body is required, prefer
--body-fileover inline--body --body-filecontent must be valid JSON encoded as UTF-8 without BOM- if inline
--bodyfails JSON parsing in PowerShell, regenerate payload as--body-fileand retry once - avoid Bash-style escaped JSON in PowerShell (for example
{\"k\":\"v\"}), it may be parsed as invalid JSON - policy payload guard (hard rule for independent resource writes):
- preflight must block
api acl roles data-source-resources create|updateandapi acl roles apply-data-permissionswhen payload is missing or invalid (--body-filepreferred,--bodycompatible) - for those writes, payload must include
usingActionsConfig: trueand non-emptyactions[] - for actions
create/view/update/export/importXlsx, each action must carry non-emptyfields[] - for every action item, scope binding must be explicit via one of:
scopeId(for explicit id binding)scopeKey(for key-based resolution, such asall/own)scope.{id|key}(compatibility readback payload)- if user intent is scope
allorown, readback must show resolved non-nullscopeId - if guard fails, stop before CLI execution and return a fixable error
- parameter safety guard:
- command shape guard for resource permissions:
roles data-source-resourcesonly hascreate|get|update; do not calllist- for
roles data-source-resources get|update, locator must be one of: --filter-by-tk <resource_config_id>--data-source-key <data_source_key> --name <collection_name>- for action-level independent-permission readback, use
roles data-source-resources get ... --appends actions - for unified independent-permission writes (single or batch collections), prefer
roles apply-data-permissions --filter-by-tk <role_name> --body-file <path> - for
roles data-sources-collections list, use--data-source-key <data_source_key>as the default locator; use--filteronly for compatibility - for collection/field resolution, prefer
nb api resource list --resource collections --filter '{}' --appends fields -jas primary metadata source - for
roles desktop-routes add, request body must be JSON array of numeric route ids - never execute write commands with uncertain, unresolved, or type-mismatched parameters
- lock execution base-dir before any ACL discovery/write (use one stable project root for the whole task)
- run execution guard sequence before ACL writes:
nb env list -s projectnb env update <current_env_name>nb api acl --helpnb api acl roles --help- fail-closed policy:
- if
nb api acl --helpornb api acl roles --helpfails, stop and return capability-boundary message; do not switch to ad-hoc script execution. - confirm current env context through direct CLI: run
nb env list -s projectand resolve current env from the row marked with* - if no env is configured/current, stop writes and ask user whether to add/switch env using direct CLI (
nb env add ...ornb env use ... -s project) - if runtime command cache is missing/stale or command schema changed, run
nb env update <current_env_name> - if runtime refresh fails with
swagger:get404 or API documentation plugin errors, activate dependency bundle and retry: nb pm enable @nocobase/plugin-api-docnb pm enable @nocobase/plugin-api-keys- restart app before retrying runtime refresh
- if token is missing/invalid, ensure
@nocobase/plugin-api-keysis active and refresh token env first - resolve runtime command names via intent-to-tool-map-v1 and command help discovery
- Plan before writes.
- list proposed change set, readback checkpoints, and blast radius
- for high-impact writes require explicit confirmation
- for resource permission writes, include:
- data source key
- resolved target collection list
- action list
- scope choice (default
allwhen omitted) - resolved scope binding (
scopeId/ scope key) - field policy (
all fieldsby default unless user provided restrictions) - resolved full-field list per action when field restrictions are omitted
- Execute one task at a time.
- keep writes minimal and scoped
- prefer ACL-specific runtime commands generated from swagger
- for
permission.data-source.resource.set, prefer unified write vianb api acl roles apply-data-permissionswith completeresources[]payload; useroles data-source-resources create|updateonly as compatibility path
- Readback verification.
- verify target data changed as requested
- include concise evidence blocks
- for independent resource permissions, use
nb api acl roles data-source-resources get ... --appends actionswhen verifying action-level scope/fields
- Risk and boundary reporting.
- return high-impact notes even on success
- use friendly boundary messaging for unsupported paths
Tooling Policy
Primary write path:
- ACL-specific CLI runtime commands (swagger-generated)
- for user-role membership writes, prefer dedicated ACL command path first (
nb api acl roles users add/remove)
Guarded fallback path (user-role membership only):
- allowed only when dedicated ACL membership command is unavailable and
allow_generic_association_write=true - use generic
nb api resource update/listonly forusers.rolesassociation operations - mandatory readback after write
Hard restrictions:
- never use direct HTTP fallback
- never use direct database mutation
- never use generic resource commands for ACL policy writes when ACL-specific runtime commands exist
- determine ACL support by checking
nb api acl --helpandnb api acl roles --helpin the same lockedbase-dirbefore concluding capability status - never create temporary
.js/.ps1/.shexecutor scripts to bypass runtime command discovery
Safety Gate
High-impact ACL actions:
- changing global role mode
- broad system snippets (
ui.*,pm,pm.*,app) - broad data-source actions (
destroy, broad export/import grants) - role assignment to many users
Safety rules:
- keep high-impact writes behind explicit confirmation
- include blast-radius summary before apply
- always perform readback verification after write
Capability Boundary Messaging
When a scenario is not supported by current CLI/runtime/tool policy:
This scenario is currently blocked by capability or governance policy in this skill.Please complete the operation in NocoBase admin UI, or enable the guarded fallback option if available.If you want, I can provide exact UI navigation steps and field suggestions.
Verification Checklist
- task normalized to canonical task
- required inputs complete before writes
- CLI capability gate passes (env context available via direct
nb env list -s project, runtime commands resolvable) - CLI dependency plugins (
@nocobase/plugin-api-doc,@nocobase/plugin-api-keys) are active or explicit recovery guidance is emitted - runtime command names resolved from command map/help
- execution guard evidence includes locked
base-dirplusnb env list -s project,nb env update <current_env_name>,nb api acl --help, andnb api acl roles --help - every write has immediate readback evidence
- for
permission.data-source.resource.set, data source + resolved collections + actions + scope were confirmed before write - when user did not provide scope, confirmation/readback shows
allas the applied default scope - for
permission.data-source.resource.set, readback confirmsusingActionsConfig=trueand action-level scope/fields in the same write cycle - for action-level verification of independent permissions, readback command includes
--appends actions - for scope=
all|own, readback shows non-nullscopeIdand matching scope key - when field rules were omitted by user, full-field defaults were applied explicitly as non-empty field-name lists
- when full-field defaults are used, readback field lists match requested names and do not silently lose system fields
- command-level preflight blocks malformed independent-resource payloads before execution (missing/invalid
usingActionsConfig,actions, scope bindingscopeId|scopeKey,fields) roles data-source-resources get|updatelocator is explicit (filterByTkordata-source-key + name) before execution- collection/field metadata is resolved through
resource collectionsread path;roles data-sources-collections listis compatibility-only for role-facing view roles desktop-routes adduses JSON array body with numeric route ids- no write executes with uncertain or type-mismatched parameters
- global role-mode tasks do not require
role_name - boundary messages are clear and actionable
Minimal Test Scenarios
global.role-mode.getandglobal.role-mode.setwith readback.role.create-blankthen verify role exists and has conservative defaults.permission.data-source.global.setand verify strategy actions.permission.data-source.resource.setwith business collection name should auto-resolve real collection name(s) from target data source.permission.data-source.resource.setwith ambiguous collection match should ask for disambiguation before write.permission.data-source.resource.setwith missing actions should ask for clarification before write.permission.data-source.resource.setwith missing scope should default toall, show this in confirmation, and allow user override before write.permission.data-source.resource.setwithviewand no field restrictions should apply full-field permission by default via explicit non-empty field lists.permission.data-source.resource.setwith scope=allshould write explicit built-in scope binding (non-nullscopeIdfor key=all).permission.data-source.resource.setshould require pre-write confirmation including data source + resolved collections + actions + scope.permission.data-source.resource.setshould write independent policy in one complete payload (usingActionsConfig + actions + scope + fields), not multi-step patching.user.assign-rolein strict mode should use dedicated membership command when available; if unavailable, block with boundary guidance.user.assign-roleguarded fallback should run only when dedicated command is unavailable and guarded mode is explicitly enabled.risk.assess-roleshould return score + evidence + recommendations.- Full-field default should preserve system fields in readback when metadata includes them.
- Wrong base-dir or missing runtime command cache must fail-closed with boundary message, not ad-hoc script fallback.
Reference Loading Map
| Reference | Use When | Notes |
|---|---|---|
| references/intent-presets-v1.md | intent normalization and defaults | includes global role-mode wording |
| references/intent-to-tool-map-v1.md | runtime command resolution | maps logical tasks to CLI command patterns |
| references/execution-guard-template.md | every ACL write task | fixed preflight command template for base-dir lock and fail-closed checks |
| references/result-format-v1.md | output rendering | includes risk cards and capability path |
| references/configuration.md | ACL policy details | detailed data-source and scope guidance |
| references/independent-permissions.md | resource-level permission writes | usingActionsConfig + actions + fields + scope complete-write policy |
| tests/capability-test-plan.md | capability matrix | aligned with v2 domains |
| tests/test-playbook.md | acceptance regression | prompt-first TC01, TC02, TC04-TC20 with runtime evidence commands |
| references/refactor-plan-v2.md | capability gaps and rollout plan | includes CLI migration notes |
| tests/README.md | runtime verification | playbook execution flow and reporting notes |