# Nacl Sa Roles

> |

- **Type:** Skill
- **Install:** `agentstack add skill-itsalt-nacl-nacl-sa-roles`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [ITSalt](https://agentstack.voostack.com/s/itsalt)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [ITSalt](https://github.com/ITSalt)
- **Source:** https://github.com/ITSalt/NaCl/tree/main/nacl-sa-roles

## Install

```sh
agentstack add skill-itsalt-nacl-nacl-sa-roles
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# /nacl-sa-roles --- System Roles & Permissions (Graph)

## Purpose

Define system roles, build the CRUD permission matrix against DomainEntities, and create BA-to-SA role mapping edges. All data lives in Neo4j -- no markdown artifacts.

**Shared references:** `nacl-core/SKILL.md`

---

## Neo4j Tools

| Tool | Usage |
|------|-------|
| `mcp__neo4j__read-cypher` | Read-only queries (fetch BA roles, existing SystemRoles, DomainEntities) |
| `mcp__neo4j__write-cypher` | Create/update/delete nodes and edges |
| `mcp__neo4j__get-schema` | Introspect current graph schema |

---

## Modes

### Mode `IMPORT_BA`

Import BA business roles from Neo4j graph as SystemRole candidates with N:M mapping and full handoff traceability.

**When:** BA layer populated in Neo4j (BusinessRole nodes exist), SA role model not yet created.

### Mode `CREATE`

Create a single new SystemRole interactively with permissions.

**When:** User asks to add a new system role not sourced from BA (e.g. Admin, System).

**Parameter:** `role_name` -- name of the role to create.

### Mode `MODIFY`

Modify an existing SystemRole: change permissions, add/remove MAPPED_TO edges, update properties.

**When:** User asks to change permissions or properties of an existing role.

**Parameter:** `role_name` -- name of the role to modify.

### Mode `FULL`

Build the complete role model: all roles, permission matrix, data scope rules.

**When:** After `nacl-sa-architect` and `nacl-sa-domain`, when building the full role model from scratch.

---

## Workflow

```
+-----------------+    +-----------------+    +-----------------+    +-----------------+
| Phase 1         |    | Phase 2         |    | Phase 3         |    | Phase 4         |
| Identify        |--->| CRUD            |--->| Data scope      |--->| Workflow        |
| roles           |    | permission      |    | constraints     |    | permissions     |
|                 |    | matrix          |    |                 |    |                 |
+-----------------+    +-----------------+    +-----------------+    +-----------------+
```

Each phase ends with:
1. **Summary** -- what was understood
2. **Confirmation** -- user approval
3. **Graph writes** -- create/update nodes and edges in Neo4j

**Do not proceed to the next phase without explicit user confirmation!**

---

## Pre-check

1. Verify DomainEntity nodes exist (needed for permission matrix):

```cypher
// mcp__neo4j__read-cypher
MATCH (de:DomainEntity)
OPTIONAL MATCH (m:Module)-[:CONTAINS_ENTITY]->(de)
RETURN de.id AS id, de.name AS name, m.name AS module
ORDER BY m.name, de.name
```

If no DomainEntities found -- suggest running `nacl-sa-domain` first.

2. Check existing SystemRole nodes:

```cypher
// mcp__neo4j__read-cypher
MATCH (sr:SystemRole)
OPTIONAL MATCH (br:BusinessRole)-[:MAPPED_TO]->(sr)
OPTIONAL MATCH (sr)-[p:HAS_PERMISSION]->(de:DomainEntity)
RETURN sr.id AS id, sr.name AS name, sr.type AS type, sr.description AS description,
       collect(DISTINCT br.full_name) AS mapped_from_ba,
       count(DISTINCT p) AS permission_count
ORDER BY sr.id
```

If SystemRoles already exist -- warn about possible overwrite and suggest `MODIFY` mode.

3. Check for available BA roles (for IMPORT_BA suggestion):

```cypher
// mcp__neo4j__read-cypher
MATCH (br:BusinessRole)
WHERE NOT (br)-[:MAPPED_TO]->(:SystemRole)
RETURN count(br) AS unmapped_ba_roles
```

If unmapped BA roles exist -- suggest starting with `IMPORT_BA` mode.

---

## Mode: IMPORT_BA

### Step 1: Read BA roles from graph

**Goal:** Fetch all BusinessRole nodes and their process associations.

**Cypher -- fetch all BA roles with context:**

```cypher
// mcp__neo4j__read-cypher
MATCH (br:BusinessRole)
OPTIONAL MATCH (br)-[:OWNS]->(owned:BusinessProcess)
OPTIONAL MATCH (br)-[:PARTICIPATES_IN]->(part:BusinessProcess)
OPTIONAL MATCH (br)-[:PERFORMED_BY]-(ws:WorkflowStep)
RETURN br.id AS id, br.full_name AS name, br.description AS description,
       collect(DISTINCT owned.name) AS owns_processes,
       collect(DISTINCT part.name) AS participates_in,
       collect(DISTINCT ws.function_name) AS performs_steps
ORDER BY br.id
```

**Cypher -- check already-mapped BA roles:**

```cypher
// mcp__neo4j__read-cypher
MATCH (br:BusinessRole)-[:MAPPED_TO]->(sr:SystemRole)
RETURN br.id AS ba_id, br.full_name AS ba_name,
       sr.id AS sa_id, sr.name AS sa_name
```

If all BA roles already mapped -- tell the user. Suggest `CREATE` or `MODIFY` mode.

**Cypher -- fetch DomainEntities for permission context:**

```cypher
// mcp__neo4j__read-cypher
MATCH (de:DomainEntity)
OPTIONAL MATCH (m:Module)-[:CONTAINS_ENTITY]->(de)
RETURN de.id AS id, de.name AS name, m.name AS module
ORDER BY m.name, de.name
```

**Cypher -- fetch UseCases for actor assignment context:**

```cypher
// mcp__neo4j__read-cypher
MATCH (uc:UseCase)
OPTIONAL MATCH (m:Module)-[:CONTAINS_UC]->(uc)
RETURN uc.id AS id, uc.name AS name, m.name AS module
ORDER BY m.name, uc.id
```

---

### Step 2: Propose N:M mapping BA -> SA

**Goal:** For each BusinessRole, propose one or more SystemRoles. Multiple BA roles may merge into one SA role. One BA role may split into multiple SA roles.

**Mapping rules:**

| Situation | Action | Example |
|-----------|--------|---------|
| 1 BA role = 1 SA role | Direct mapping (1:1) | "Менеджер" -> "Manager" |
| 1 BA role -> N SA roles | Split by access level (1:N) | "Руководитель" -> "TeamLead", "DepartmentHead" |
| N BA roles -> 1 SA role | Merge similar system rights (N:1) | "Кладовщик", "Приёмщик" -> "WarehouseOperator" |
| No BA source | System-only role (new) | -- -> "Admin", "System" |

**Always consider adding these system-only roles if not present in BA:**
- **Admin** -- full system access, user management, configuration
- **System** -- automated/background processes (if the system has scheduled tasks, integrations, etc.)

**Present to user:**

```
**BA -> SA Role Mapping**

Found {N} BA business roles -> proposing {M} SA system roles:

| # | BA Role(s) | SA Role (candidate) | Type | Mapping |
|---|-----------|--------------------|----|---------|
| 1 | {BA Role 1} | {SA Role 1} | internal | 1:1 |
| 2 | {BA Role 2}, {BA Role 3} | {SA Role 2} | internal | N:1 (merge) |
| 3 | {BA Role 4} | {SA Role 3a}, {SA Role 3b} | external | 1:N (split) |
| 4 | -- | Admin | internal | New SA role |
| 5 | -- | System | internal | New SA role |

Questions:
1. Is the proposed BA -> SA mapping correct?
2. Should any roles be split or merged differently?
3. Which additional system-only roles are needed?
4. Role types: internal (employee) or external (client, partner)?
```

---

### Step 3: Create SystemRole nodes and MAPPED_TO edges

**Goal:** Write SystemRole nodes and BA->SA handoff edges to Neo4j.

**Cypher -- get next SystemRole ID:**

```cypher
// mcp__neo4j__read-cypher
MATCH (sr:SystemRole)
WITH max(toInteger(replace(sr.id, 'SR-', ''))) AS maxNum
RETURN 'SR-' + apoc.text.lpad(toString(coalesce(maxNum, 0) + 1), 2, '0') AS nextId
```

If `apoc` is not available, compute the next ID in the agent and pass it as a parameter.

**Cypher -- create SystemRole:**

```cypher
// mcp__neo4j__write-cypher
MERGE (sr:SystemRole {id: $id})
SET sr.name = $name,
    sr.type = $type,
    sr.description = $description,
    sr.responsibilities = $responsibilities,
    sr.system_only = $systemOnly,
    sr.status = 'draft',
    sr.created = datetime()
```

Parameters:
- `$id` -- format `SR-NN` (e.g. "SR-01")
- `$name` -- English PascalCase (e.g. "OrderManager")
- `$type` -- "internal" or "external"
- `$description` -- Russian description of the role
- `$responsibilities` -- semicolon-separated list of main responsibilities (Russian)
- `$systemOnly` -- boolean. `true` for infrastructure-only roles that never appear in BA mapping (e.g., `System`, `Worker`, `Cron`); `false` for end-user-facing roles (Teacher, Student, OrderManager, etc.). Used by validator XL8.2 to skip BA-mapping requirements on infrastructure roles. If forgotten, `nacl-sa-flags backfill-all` defaults it to `false` and the user can flip it later via `/nacl-sa-flags set-system-only --role  true`.

**Cypher -- create MAPPED_TO handoff edge (BusinessRole -> SystemRole):**

```cypher
// mcp__neo4j__write-cypher
MATCH (br:BusinessRole {id: $baRoleId}), (sr:SystemRole {id: $saRoleId})
MERGE (br)-[:MAPPED_TO]->(sr)
```

Execute for each BA-to-SA pair in the confirmed mapping table.

**After all roles created, verify:**

```cypher
// mcp__neo4j__read-cypher
MATCH (sr:SystemRole)
OPTIONAL MATCH (br:BusinessRole)-[:MAPPED_TO]->(sr)
RETURN sr.id AS id, sr.name AS name, sr.type AS type,
       collect(DISTINCT {ba_id: br.id, ba_name: br.full_name}) AS mapped_from
ORDER BY sr.id
```

**Present summary:**

```
**Step 3 complete: SystemRole nodes created**

| SA Role | ID | Type | Mapped from BA |
|---------|-----|------|---------------|
| {name} | {id} | {type} | {ba_names or "New SA role"} |

Total: {N} SystemRole nodes, {M} MAPPED_TO edges
```

After confirmation -> proceed to Phase 2 (CRUD matrix).

---

## Phase 1: Identify Roles (FULL / CREATE modes)

**Goal:** Determine all system roles.

### Mode FULL

If BA roles exist in the graph, start with IMPORT_BA Steps 1-3 to pre-populate. Then continue here to review and add system-only roles.

If no BA roles -- gather roles from user.

**Cypher -- load context for role proposal:**

```cypher
// mcp__neo4j__read-cypher
MATCH (m:Module)
OPTIONAL MATCH (m)-[:CONTAINS_UC]->(uc:UseCase)
OPTIONAL MATCH (m)-[:CONTAINS_ENTITY]->(de:DomainEntity)
RETURN m.name AS module,
       collect(DISTINCT uc.name) AS use_cases,
       collect(DISTINCT de.name) AS entities
ORDER BY m.name
```

**Present to user:**

```
Based on the system architecture and domain model I propose the following roles:

1. **{Role 1}** ({type: internal/external}) -- {description, what the role does}
2. **{Role 2}** ({type}) -- {description}
3. **Admin** (internal) -- full system access, configuration, user management

Questions:
1. Are the proposed roles correct?
2. Any additional roles?
3. Can one user hold multiple roles?
4. Is there a role hierarchy (permission inheritance)?
```

### Mode CREATE

Ask user for role details:

```
**New SystemRole: {name}**

1. English name (PascalCase): {suggestion}
2. Type: internal / external?
3. Description: ?
4. Main responsibilities (3-5 items): ?
5. Map to existing BA role? (optional)

Confirm?
```

Create the SystemRole node using the Cypher from IMPORT_BA Step 3.

### Rules

- Minimum 2 roles (otherwise no role model needed)
- Maximum 7 roles (otherwise reconsider decomposition)
- Each role must differ from others by at least one module or access level
- Guest / unauthenticated user -- separate role if public access exists
- Admin -- always present if the system requires configuration

---

## Phase 2: CRUD Permission Matrix

**Goal:** Define access for each role to each DomainEntity (Create, Read, Update, Delete).

### Step 2.1: Fetch all DomainEntities

```cypher
// mcp__neo4j__read-cypher
MATCH (de:DomainEntity)
OPTIONAL MATCH (m:Module)-[:CONTAINS_ENTITY]->(de)
RETURN de.id AS id, de.name AS name, m.name AS module
ORDER BY m.name, de.name
```

### Step 2.2: Fetch all SystemRoles

```cypher
// mcp__neo4j__read-cypher
MATCH (sr:SystemRole)
RETURN sr.id AS id, sr.name AS name, sr.type AS type
ORDER BY sr.id
```

### Step 2.3: Propose CRUD matrix

Build a table: rows = DomainEntities, columns = SystemRoles. For each cell determine: C/R/U/D or combination.

Use BA context to inform proposals:
- If a BA role OWNS a process that PRODUCES an entity -> C (create)
- If a BA role PARTICIPATES_IN a process that READS an entity -> R (read)
- If a BA role performs steps that MODIFY an entity -> U (update)
- Admin gets CRUD on everything

**Cypher -- BA role-entity interaction (for informed proposals):**

```cypher
// mcp__neo4j__read-cypher
MATCH (br:BusinessRole)-[:PERFORMED_BY]-(ws:WorkflowStep)
MATCH (ws)-[rel:READS|PRODUCES|MODIFIES]->(be:BusinessEntity)
MATCH (be)-[:REALIZED_AS]->(de:DomainEntity)
OPTIONAL MATCH (br)-[:MAPPED_TO]->(sr:SystemRole)
RETURN sr.name AS system_role,
       de.name AS domain_entity,
       collect(DISTINCT type(rel)) AS interactions
ORDER BY sr.name, de.name
```

**Present to user:**

```
**CRUD Permission Matrix**

| DomainEntity | {Role 1} | {Role 2} | {Role 3} | Admin |
|-------------|----------|----------|----------|-------|
| {Entity 1}  | CR       | R        | --       | CRUD  |
| {Entity 2}  | CRUD     | R        | R(own)   | CRUD  |
| {Entity 3}  | --       | CRU      | R        | CRUD  |

Legend:
- C = Create, R = Read, U = Update, D = Delete
- R(own) = read only own records
- CRU(own) = create, read, update only own records
- -- = no access

Questions:
1. Are the access levels correct?
2. Any entities that should be restricted further?
3. Should we distinguish "list view" vs "detail view"?
```

### Step 2.4: Write HAS_PERMISSION edges to Neo4j

**Cypher -- create HAS_PERMISSION edge:**

```cypher
// mcp__neo4j__write-cypher
MATCH (sr:SystemRole {id: $roleId}), (de:DomainEntity {id: $entityId})
MERGE (sr)-[p:HAS_PERMISSION]->(de)
SET p.crud = $crud,
    p.scope = $scope
```

Parameters:
- `$roleId` -- SystemRole.id (e.g. "SR-01")
- `$entityId` -- DomainEntity.id (e.g. "DE-Order")
- `$crud` -- combination of C/R/U/D letters (e.g. "CRUD", "CR", "R")
- `$scope` -- data visibility scope: "all", "own", "department", "assigned" (default "all")

Execute for each confirmed (role, entity) pair from the matrix.

**CRUD notation rules:**
- `"CRUD"` -- full access
- `"CR"` -- create and read
- `"R"` -- read only
- `"RU"` -- read and update
- `""` (empty string) or no edge -- no access (do not create HAS_PERMISSION edge for no-access pairs)

### Step 2.5: Verify permission matrix

```cypher
// mcp__neo4j__read-cypher
MATCH (sr:SystemRole)-[p:HAS_PERMISSION]->(de:DomainEntity)
RETURN sr.name AS role, de.name AS entity, p.crud AS crud, p.scope AS scope
ORDER BY sr.name, de.name
```

**Present summary:**

```
**Phase 2 complete: CRUD matrix written to graph**

| DomainEntity | {Role 1} | {Role 2} | ... | Admin |
|-------------|----------|----------|-----|-------|
| {Entity}    | {crud}   | {crud}   | ... | CRUD  |

Total: {N} HAS_PERMISSION edges
```

---

## Phase 3: Data Scope Constraints

**Goal:** Define which records each role can see (row-level security).

### Questions

```
For each role, let's define data visibility:

1. **{Role 1}**: Sees {all data / only own / only department}?
2. **{Role 2}**: Sees {all data / only own}?

Questions:
1. Should data be filtered by ownership?
2. Is there an organizational hierarchy (departments, branches)?
3. Should a manager see other managers' data?
```

### Scope Values

| Scope | Description | Filter Rule |
|-------|-------------|-------------|
| `all` | Sees all records | No filter |
| `own` | Only records where user = creator/owner | `entity.owner_id = current_user.id` |
| `department` | Records from user's department | `entity.department_id = current_user.department_id` |
| `assigned` | Records explicitly assigned to user | `entity.assignee_id = current_user.id` |

### Update HAS_PERMISSION scope

For each (role, entity) pair that needs scope restriction:

```cypher
// mcp__neo4j__write-cypher
MATCH (sr:SystemRole {id: $roleId})-[p:HAS_PERMISSION]->(de:DomainEntity {id: $entityId})
SET p.scope = $scope,
    p.scope_rule = $scopeRule
```

Parameters:
- `$scope` -- "all", "own", "department", "assigned"
- `$scopeRule` -- human-readable filter rule (e.g. "order.client_id = current_user.id")

### Verify data scope

```cypher
// mcp__neo4j__read-cypher
MATCH (sr:SystemRole)-[p:HAS_PERMISSION]->(de:DomainEntity)
WHERE p.scope <> 'all'
RETURN sr.name AS role, de.name AS entity, p.crud AS crud,
       p.scope AS scope, p.scope_rule AS scope_rule
ORDER BY sr.name, de.name
```

**Present summary:**

```
**Phase 3 complete: Data scope constraints**

| Role | Entity | CRUD | Scope | Rule |
|------|--------|------|-------|------|
| {role} | {entity} | {crud} | {scope}

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [ITSalt](https://github.com/ITSalt)
- **Source:** [ITSalt/NaCl](https://github.com/ITSalt/NaCl)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-itsalt-nacl-nacl-sa-roles
- Seller: https://agentstack.voostack.com/s/itsalt
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
