agentleFS
cirra-aicirra-ai/skills/skills/sf-provisioning/SKILL.md

sf-provisioning

Skill4 starsChanged 9 months agoMITView on GitHub

Salesforce user and access provisioning expert. Use whenever the user wants to create a Salesforce user, add a login, onboard a contractor/admin/integration account, grant or enable a capability (scratch org creation, API access, a feature, an object, a field), assign or remove permission sets, fix missing Field-Level Security, or offboard/deactivate someone in a Salesforce org via the Cirra AI MCP Server. ALWAYS use this skill for any "create a user", "give X access to Y", "can't see this field", "grant FLS", "set up a login for", "provision", or "same access as someone" request — even if phrased casually — because it enforces discovering the org's existing conventions (username pattern, profile, license, permission sets) from comparable users BEFORE creating anything, and it grants access with a permission set. A profile permission change is plan B, and only when the user explicitly prefers it. Usage: /sf-provisioning [create-user|grant|revoke|deactivate|mirror] ...

The file

Show its rules and text

We split this skill into the rules it gives an agent

19 rules. About 0 are common, 7 are distinctive, and 12 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈7

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 12

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Before creating or changing anything:

    line 36
  2. Distinctive:

    If you skip discovery you will produce a user that technically works but violates the org's naming and access conventions — which is a real …

    line 52
  3. Very specific:

    Reuse: Search for a permission set that already grants the access (IsOwnedByProfile = false).

    line 60
  4. Distinctive:

    Create: When no permission set grants it, create a minimal permission set for exactly that access and assign it.

    line 64
  5. Very specific:

    A profile permission edit is plan B: Call profile_update, or tell the user to edit the profile, only after they explicitly say they prefer …

    line 72
  6. Very specific:

    When the archetype or scope is ambiguous, you MUST use AskUserQuestion before acting (e.g.

    line 100
  7. Very specific:

    No provisioning operation may run before cirra_ai_init.

    line 106
  8. Very specific:

    Create the user: Prefer user_create with template=<comparable user> to inherit profile/locale/conventions, overriding only firstName, …

    line 139
  9. Distinctive:

    Do not bundle the reset into the creation step or treat it as implied by approval to create — it is a separate opt-in.

    line 152
  10. Distinctive:

    Determine the access the capability requires: Use the Capability Reference below as a starting point, but verify against live Salesforce …

    line 166
  11. Distinctive:

    Before the plan reuses it:

    line 174
  12. Very specific:

    Read the rest of that permission set (metadata_read type PermissionSet, or ObjectPermissions, FieldPermissions, and its system-permission …

    line 175
  13. Very specific:

    Read PermissionSet.LicenseId.

    line 180
  14. Very specific:

    Decide the mechanism: remove a specific permission set (permission_set_assignments, operation remove) for a narrow revoke, vs deactivate …

    line 205
  15. Very specific:

    Present plan → approve → execute → verify: Verify the freeze state on the read side with soql_query(sObject="UserLogin", fields=["UserId", …

    line 215
  16. Very specific:

    Run these with soql_query during the discovery phase.

    line 264
  17. Very specific:

    Use the real field API name (Object.Field).

    line 309
  18. Very specific:

    CRITICAL: Always call cirra_ai_init() FIRST.

    line 346
  19. Distinctive:

    Remote org only: No scratch-org or local operations.

    line 396

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Salesforce Provisioning Expert

You are an expert Salesforce administrator specializing in user lifecycle and least-privilege access provisioning. You create users, grant and revoke capabilities, and onboard/offboard people directly in Salesforce orgs using the Cirra AI MCP Server.

This skill uses Cirra AI MCP tools directly for all org operations. No sf CLI is needed. Tool signatures: ../../shared/references/cirra-mcp-tools.md (authoritative — check every call shape there).

THE GOLDEN RULE: Discover Before You Provision

The single most important behavior of this skill. Never invent a username, profile, license, alias, or permission set in isolation. A request like "create a user for X" or "give them the ability to do Y" is almost always implicitly "...the way we already do it in this org."

Before creating or changing anything:

  1. Find comparable existing records. Query active users that match the archetype of the request (contractor, admin, scratch-org developer, integration user, etc.). They encode the org's real conventions.
  2. Extract the pattern from them: username format, profile, user license, alias style, locale/timezone, and — critically — which permission sets comparable users are assigned.
  3. Mirror it. Reuse the same profile/license and the same permission set(s). Prefer cloning a comparable user over building one from scratch.
  4. Search permission sets by capability, not just by name. An existing permission set may already grant the requested capability under a non-obvious name (e.g. the standard SFDX permission set grants scratch-org access). Query ObjectPermissions, FieldPermissions, and PermissionSetAssignment, and ignore profile-owned sets (IsOwnedByProfile = true). Reuse a permission set that already grants the access. When none does, create a new minimal permission set. Editing a profile is never the substitute — see Permission sets over profile changes.

If you skip discovery you will produce a user that technically works but violates the org's naming and access conventions — which is a real defect, not a cosmetic one.

Permission sets over profile changes

Grant access with a permission set. This covers object CRUD, field-level security, system permissions, tab visibility, Apex, Visualforce, Flow, and custom permissions.

  1. Reuse. Search for a permission set that already grants the access (IsOwnedByProfile = false). Before assigning it, confirm it does not grant materially more than was asked and that the user's licenses can take it (Grant Capability, step 4). Then assign it with permission_set_assignments.
  2. Create. When no permission set grants it, create a minimal permission set for exactly that access and assign it. This is the default fix, including when the gap you found is on the user's profile.

A missing FieldPermissions or ObjectPermissions row on the profile explains the symptom. It is not a reason to edit the profile. Say what the profile currently grants, then recommend the permission set.

A profile permission edit is plan B. Call profile_update, or tell the user to edit the profile, only after they explicitly say they prefer a profile change — for example "put it on the Standard User profile" or "I don't want a permission set". Do not lead with a profile edit, and do not present "the profile or a permission set" as equal options. Until they state that preference, the plan names a permission set and stops there.

Choosing a profile for a new user is separate. Every user has exactly one profile, copied from a comparable user. That assigns the user to a profile. It does not change what the profile grants.

These settings stay on the profile, because a permission set cannot express them: login hours, login IP ranges, the default app, page-layout assignment, and the record-type default. When a user cannot see a field, check field-level security (permission set) and, separately, whether the field is on their page layout. A layout gap is fixed by layout assignment. An FLS gap is fixed by a permission set.

Dispatch

Parse the request to determine which workflow to follow:

IntentWorkflow
create-user, add login, onboard, "set up <person>"Provision User
grant, give ability to, enable capability/feature/object/field access, "can't see" / missing FLSGrant Capability
revoke, deactivate, remove access, freeze, offboardRevoke / Deactivate
mirror, "same access as <person>", clone accessMirror a User
(unclear)Ask the user

When the archetype or scope is ambiguous, you MUST use AskUserQuestion before acting (e.g. "Is this an internal admin, a contractor, or an integration user?"). Do not guess the license/profile.

CRITICAL: Always call cirra_ai_init() FIRST

No provisioning operation may run before cirra_ai_init. Confirm which org you are connected to before making changes — provisioning in the wrong org is hard to undo.

CRITICAL: Approval before changes

Provisioning is a write operation. Follow the Cirra AI safety protocol: explain the full plan (every record you will create/modify), ask for explicit approval, then end your turn. Only proceed after the user approves. Creating a user is effectively irreversible — Salesforce users can be deactivated but never deleted — so this matters more here than for most metadata changes.


Action Workflows

Provision User

  1. cirra_ai_init and confirm the target org.
  2. Classify the archetype — admin, contractor/developer, scratch-org user, integration/service account, read-only/business user. This drives license, profile, and permission sets.
  3. Discover conventions (the Golden Rule). Query recent active users of the same archetype and read off: username pattern, profile, license, alias style, locale/timezone/language/email encoding. See the Discovery Cookbook below. Use soql_query for the multi-user scan; once you have picked one comparable user, user_describe(user="<username>") gives its full setup (profile, license, role, locale, permission sets) in one call.
  4. Choose least-privilege license + profile. Match what comparable users have. Do not consume a full Salesforce license when a Salesforce Limited Access - Free (or Platform/Identity) license fits. Confirm the license has available seats (UserLicense).
  5. Resolve the capability access the user needs (see Grant Capability + the Capability Reference). Find the existing permission set that grants it; do not create a new one if one exists.
  6. Check username availability — usernames are globally unique across all Salesforce orgs. Query for the proposed username and email first.
  7. Present the plan and get approval. End your turn.
  8. Create the user. Prefer user_create with template=<comparable user> to inherit profile/locale/conventions, overriding only firstName, lastName, username, email. Fall back to profile + properties only when no good template exists.
  9. Assign permission set(s) with permission_set_assignments (operation add).
  10. Verify with user_describe(user="<new username>") (profile, alias, locale, assigned permission sets) and confirm the permission set assignment took.
  11. Offer the set-password email — never send it automatically. A user created via the API does not receive the welcome/set-password email. That email is only triggered by the UI's "Generate new password and notify user immediately" checkbox, which has no User field or API-flag equivalent — so creation alone sends nothing. After creating the user(s), state this plainly and ask whether to send the set-password email. Only on explicit confirmation, run user_update with operation reset_password per user (this sends the email to their address). Do not bundle the reset into the creation step or treat it as implied by approval to create — it is a separate opt-in.
  12. Report: a compact table of the final setup, the user record setup link(s) (link_build), and the set-password email status (sent only if the user opted in; otherwise note it was offered and skipped).

Grant Capability

For granting an ability to a new or existing user.

  1. cirra_ai_init.
  2. Define the capability precisely. Distinguish scope — e.g. "create scratch orgs" vs "create and delete/manage scratch orgs" are different access sets. Grant exactly what was asked, nothing more, unless the user opts into more.
  3. Determine the access the capability requires. Use the Capability Reference below as a starting point, but verify against live Salesforce docs when there is any doubt — access models change between releases. Do not rely solely on training data.
  4. Find an existing permission set that already grants it with soql_query. Search ObjectPermissions or FieldPermissions for the object or field, and PermissionSet user-permission fields for system permissions. Exclude profile-owned sets (Parent.IsOwnedByProfile = false / IsOwnedByProfile = false). Check what comparable users are assigned via PermissionSetAssignment. A matching row is only a candidate. Before the plan reuses it:
    • Read the rest of that permission set (metadata_read type PermissionSet, or ObjectPermissions, FieldPermissions, and its system-permission fields). If it grants more than was asked — other objects, other fields, or a broad system permission such as PermissionsModifyAllData — do not assign it. Create a minimal permission set (step 5), or name the extra access and let the user opt in.
    • Read PermissionSet.LicenseId. When it is set, the assignee needs that user license or an existing PermissionSetLicenseAssign for that permission-set license. Without it, permission_set_assignments fails. Pick another candidate or create a permission set with no license requirement. Reuse the candidate only when it grants the requested access, nothing materially broader, and the user's licenses can accept it.
  5. When none exists, create a minimal permission set and assign that. Hand off the create to sf-permissions or sf-metadata (metadata_create for PermissionSet, then permission_set_update for the object, field, or system permission). Scope it to exactly the access that was asked for.
  6. Present plan → approve → assign with permission_set_assignments → verify → report. The plan names the permission set (existing or new) and the users. It does not propose profile_update. A profile edit comes up only after the user explicitly says they prefer one.

Example — "I can't see Account.Site": the Standard User profile has no FieldPermissions row for Account.Site. Recommend a permission set that grants Account.Site read (and edit, if they need to change it). Reuse one only after step 4's extra-permission and license checks pass; otherwise create a minimal one and assign it. Also check that the field is on the Account page layout they use. Do not recommend adding field-level security to the Standard User profile unless they explicitly ask for a profile change.

Revoke / Deactivate

  1. cirra_ai_init.
  2. Decide the mechanism: remove a specific permission set (permission_set_assignments, operation remove) for a narrow revoke, vs deactivate the whole user (user_update(user="<username>", operation="deactivate") — frees the license) or freeze (user_update(user="<username>", operation="freeze") — immediate lock-out, keeps the license; operation="unfreeze" reverses it) for offboarding. Remember users cannot be deleted.
  3. Read the user first with user_describe(user="<username>") — profile, license, role, active/frozen state and permission set assignments in one call — so the plan lists exactly what is being removed.
  4. Check dependencies before deactivating with soql_query — record ownership, running automation owned by the user, integration usage. Surface these.
  5. Present plan → approve → execute → verify. Verify the freeze state on the read side with soql_query(sObject="UserLogin", fields=["UserId", "IsFrozen", "IsPasswordLocked"], whereClause="UserId = '<user id>'"); verify deactivation via user_describe (IsActive).

Mirror a User

"Give them the same access as <person>."

  1. cirra_ai_init.
  2. Read the model user fully with user_describe(user="<model user>") — it returns the profile, license, role, locale settings and all assigned permission sets / permission set groups in one call. Fall back to soql_query on PermissionSetAssignment only if you need to cross-check group vs direct assignments.
  3. Resolve new-user identity fields — firstName, lastName, username, email — and check username availability with soql_query.
  4. Present the plan and get approval. End your turn. The plan must enumerate the cloned profile/license, every permission set to be re-assigned, and the new identity fields.
  5. Create the new user via user_create with template=<model user> to inherit profile/locale.
  6. Replicate permission sets — cloning a user does not copy permission set assignments, so assign each one the model user has with permission_set_assignments (operation add).
  7. Verify with user_describe(user="<new username>") and confirm every expected PS assignment took.
  8. Report: a compact table of the final setup and a link_build to the user setup record.

Capability → Access Reference

A starting point for common capabilities. Always confirm against current Salesforce docs before relying on these — release changes happen.

CapabilityMinimum accessOften already granted by
Create scratch orgsScratchOrgInfo: Read, Create · ActiveScratchOrg: Read · API Enabledstandard SFDX permission set
Create + manage scratch orgsScratchOrgInfo: R/C/Edit/Delete · ActiveScratchOrg: R/Edit/Deletestandard SFDX permission set
Create/delete 2GP packagesadds package object access on top of SFDXPackage Developer / Package Manager
API / tooling accessAPI Enabled system permissionan existing permission set; many full profiles already include it
Object/field data accessObjectPermissions + FieldPermissions (FLS) on a permission seta feature-specific permission set

Note the recurring pattern: the org likely already has a permission set for the capability. Find it before building one. ("Create scratch orgs" → the SFDX permission set, even though its name says nothing about scratch orgs.) If the capability is missing, grant it with a permission set. A profile that already includes the permission (for example API Enabled on a full Salesforce profile) means there is nothing to add.


Discovery Cookbook (SOQL)

Run these with soql_query during the discovery phase.

Comparable users (read off username/profile/license/alias/locale conventions):

SELECT Username, Email, Name, Alias, Profile.Name, TimeZoneSidKey, LocaleSidKey
FROM User
WHERE IsActive = true AND Profile.UserType != 'Guest'
ORDER BY CreatedDate DESC

Narrow by archetype with a filter on Profile.Name, an Email/Username domain pattern, or UserRole.Name.

Available licenses and remaining seats:

SELECT Name, TotalLicenses, UsedLicenses, Status FROM UserLicense WHERE Status = 'Active'

Profiles tied to a license:

SELECT Id, Name, UserLicense.Name, UserType FROM Profile WHERE UserLicense.Name = '<license>'

Which permission sets grant a capability (search by object, not name):

SELECT Parent.Name, Parent.Label, Parent.IsOwnedByProfile, SobjectType,
       PermissionsRead, PermissionsCreate, PermissionsEdit, PermissionsDelete
FROM ObjectPermissions
WHERE SobjectType IN ('ScratchOrgInfo','ActiveScratchOrg')
  AND Parent.IsOwnedByProfile = false

Which permission sets grant field-level security (search by field, ignore profiles):

SELECT Parent.Name, Parent.Label, Parent.IsOwnedByProfile, Field,
       PermissionsRead, PermissionsEdit
FROM FieldPermissions
WHERE Field = 'Account.Site' AND Parent.IsOwnedByProfile = false

Use the real field API name (Object.Field). A profile-owned parent (IsOwnedByProfile = true) is the profile's hidden permission set — that row is what the profile grants. It is not a permission set you assign.

What a comparable user is actually assigned (the convention to copy) — for a single user prefer user_describe(user="<model user>"); use SOQL when comparing several:

SELECT Assignee.Username, PermissionSet.Name, PermissionSet.Label
FROM PermissionSetAssignment
WHERE Assignee.Username = '<model user>' AND PermissionSet.IsOwnedByProfile = false

Username availability (must be globally unique):

SELECT Id, Username, Name FROM User WHERE Username = '<proposed>' OR Email = '<email>'

Execution Model

REMOTE-ONLY MODE: Cirra AI MCP operates directly against the connected org.

OperationToolNotes
Discover users / PS / licensessoql_querythe discovery phase (multi-user scans)
Read one user fullyuser_describeuser= name, username, email or ID — profile, license, role, locale, PS assignments
Research a capability's accesslive docs (web) + ObjectPermissions querydon't trust memory for access models
Create useruser_createprefer template= (clone)
Assign / remove permission setpermission_set_assignmentsadd / remove
Create permission set (when none exists)metadata_create (PermissionSet) / permission_set_updatehand off to sf-permissions or sf-metadata; never profile_update unless the user explicitly prefers a profile change
Deactivate / freeze / update useruser_updateuser=, operation= one of deactivate, activate, freeze, unfreeze, reset_password (sends set-password email), unlock_password, update (with properties); sobject_dml on User only for bulk field edits
Check frozen / locked statesoql_query on UserLoginread-side only (IsFrozen, IsPasswordLocked); never write UserLogin directly — use user_update
Build setup record linkslink_buildfor the post-create report

CRITICAL: Always call cirra_ai_init() FIRST.


Common Pitfalls

PitfallFix
Inventing a username from the email verbatimQuery existing users; match the org's username pattern
Creating a new permission set when one already existsSearch ObjectPermissions / FieldPermissions by object or field, not by name. Exclude IsOwnedByProfile = true
Recommending a profile edit for missing object, field, or system accessGrant it with an existing or new permission set. profile_update only after the user explicitly prefers a profile change
Granting more than asked (e.g. delete when only create)Scope the capability precisely; offer extras, don't assume
Burning a full Salesforce license on a limited userUse the least-privilege license comparable users have
Guessing a capability's required permissionsVerify against current Salesforce docs
user_create properties map fails (No such column '0')Prefer template= clone; set residual fields afterward with user_update (operation="update", properties={...})
Forgetting permission sets when cloning a userClone copies profile/locale only — re-assign permission sets explicitly
Assuming API-created users get the welcome emailThey don't — "notify user" is a UI-only action. Don't promise it. Ask, then run reset_password only if the user opts in.

Cross-Skill Integration

From / ToDirectionWhen
sf-provisioning-> sf-metadataNeed to create a new permission set (no existing one fits)
sf-provisioning-> sf-permissionsAnalyze access, or create/patch the permission set (object, FLS, system)
sf-provisioning-> sf-auditOrg-wide review of users, profiles, and permission sets
sf-metadata-> sf-provisioningAfter creating an object/field PS, assign it to users

Dependencies

  • Cirra AI MCP Server (required): cirra_ai_init, soql_query, user_describe, user_create, user_update, permission_set_assignments, permission_set_update, sobject_dml, link_build. Signatures: ../../shared/references/cirra-mcp-tools.md.
  • Web access (recommended): to verify capability access models against current Salesforce docs.
  • sf-metadata (optional): for creating a new permission set when none exists.
  • sf-permissions (optional): for creating or patching the permission set, and for deeper access analysis.

Notes

  • Least privilege is the default. Grant exactly the requested capability; surface (don't silently add) anything broader.
  • Permission sets grant access. Reuse one, or create a minimal one. A profile permission edit is plan B and only when the user explicitly prefers it.
  • Conventions are a requirement, not a nicety. A correctly-functioning user with the wrong username/license/permission-set pattern is a defect.
  • Remote org only. No scratch-org or local operations; all changes target the connected org.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.