# Nacl Sa Domain

> |

- **Type:** Skill
- **Install:** `agentstack add skill-itsalt-nacl-nacl-sa-domain`
- **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-domain

## Install

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

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

## About

# /nacl-sa-domain — Domain Model (Graph)

## Purpose

Create and manage the Domain Model through Neo4j graph: entities, attributes, enumerations, inter-entity relationships, and BA-to-SA handoff 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 entities, check existing domain model) |
| `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 entities from Neo4j graph as DomainEntity candidates with full handoff traceability.

**When:** BA layer populated in Neo4j, SA domain model not yet created for those entities.

**Parameter:** `module` (optional) -- target module name. If omitted, imports all uncovered BA entities.

### Mode `CREATE`

Create a single new DomainEntity interactively with attributes, enumerations, and relationships.

**When:** User asks to add a new domain entity not sourced from BA.

**Parameter:** `entity_name` -- name of the entity to create.

### Mode `MODIFY`

Modify an existing DomainEntity: add/remove/change attributes, relationships, enumerations.

**When:** User asks to change an attribute, type, relationship, or cardinality on an existing entity.

**Parameter:** `entity_name` -- name of the entity to modify.

### Mode `FULL`

Create the complete domain model for a module: all entities, attributes, enumerations, relationships, lifecycle states, business rules.

**When:** After `nacl-sa-architect`, building full domain model for a module from scratch.

**Parameter:** `module` -- module name (must exist as a Module node in the graph).

| Parameter | Required | Description |
|-----------|----------|-------------|
| `--lang` | No | Output language: `en` or `ru` (default: `ru`). |

---

## Language

Supports `--lang=en` for English output. See [nacl-core/lang-directive.md](../nacl-core/lang-directive.md).
When `--lang=en`: all generated text, node names, descriptions in English.
Default: Russian (ru).

---

## Workflow: Mode IMPORT_BA

```
+-----------------+    +-----------------+    +-----------------+    +-----------------+
| Step 1          |    | Step 2          |    | Step 3          |    | Step 4          |
| Read uncovered  |--->| Classify &      |--->| Create nodes    |--->| Create handoff  |
| BA entities     |    | confirm types   |    | & attributes    |    | & rel edges     |
+-----------------+    +-----------------+    +-----------------+    +-----------------+
```

Each step ends with a summary and user confirmation before proceeding.

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

---

### Step 1: Read uncovered BA entities

**Goal:** Find all BusinessEntity nodes (type: "Бизнес-объект") that have no REALIZED_AS edge yet.

**Cypher -- fetch uncovered BA entities:**

```cypher
// handoff_uncovered_entities
MATCH (be:BusinessEntity {type: "Бизнес-объект"})
WHERE NOT (be)-[:REALIZED_AS]->(:DomainEntity)
RETURN be.id AS id, be.name AS name, be.type AS type, be.description AS description
```

Execute via `mcp__neo4j__read-cypher`.

**If no results:** Tell the user all BA entities are already covered. Suggest `CREATE` or `FULL` mode instead.

**Cypher -- fetch all uncovered BA entities with attributes:**

```cypher
// ba_uncovered_entities_with_attributes
MATCH (be:BusinessEntity {type: "Бизнес-объект"})
WHERE NOT (be)-[:REALIZED_AS]->(:DomainEntity)
OPTIONAL MATCH (be)-[:HAS_ATTRIBUTE]->(ea:EntityAttribute)
RETURN be.id AS entity_id, be.name AS entity_name, be.description AS entity_desc,
       collect({
         id: ea.id,
         name: ea.name,
         data_type: ea.data_type,
         description: ea.description
       }) AS attributes
ORDER BY be.id
```

**Also fetch BA relationships between uncovered entities:**

```cypher
// ba_uncovered_entity_relationships
MATCH (be1:BusinessEntity {type: "Бизнес-объект"})-[r:RELATES_TO]->(be2:BusinessEntity {type: "Бизнес-объект"})
WHERE NOT (be1)-[:REALIZED_AS]->(:DomainEntity)
   OR NOT (be2)-[:REALIZED_AS]->(:DomainEntity)
RETURN be1.id AS source_id, be1.name AS source_name,
       r.rel_type AS rel_type, r.cardinality AS cardinality,
       be2.id AS target_id, be2.name AS target_name
```

**Also fetch BA entity states (for lifecycle mapping later):**

```cypher
// ba_uncovered_entity_states
MATCH (be:BusinessEntity {type: "Бизнес-объект"})-[:HAS_STATE]->(st:EntityState)
WHERE NOT (be)-[:REALIZED_AS]->(:DomainEntity)
OPTIONAL MATCH (st)-[t:TRANSITIONS_TO]->(st2:EntityState)
RETURN be.id AS entity_id, be.name AS entity_name,
       st.id AS state_id, st.name AS state_name, st.description AS state_desc,
       t.condition AS transition_condition,
       st2.name AS target_state
```

**Also check which non-"Бизнес-объект" BA entities exist (for user awareness):**

```cypher
// ba_other_entity_types
MATCH (be:BusinessEntity)
WHERE be.type <> "Бизнес-объект"
  AND NOT (be)-[:REALIZED_AS]->(:DomainEntity)
RETURN be.id AS id, be.name AS name, be.type AS type, be.description AS description
```

**Present to user:**

```
**Uncovered BA entities found: {count}**

**Бизнес-объекты (will create DomainEntity):**
1. {name} ({id}) -- {description}
   Attributes: {attr_count}
   States: {state_count}

**Other types (need decision):**
- {name} ({id}, type: {type}) -- needs SA entity? (Y/N)

Proceed with import?
```

---

### Step 2: Classify and confirm SA types

**Goal:** For each BA entity, propose SA attribute types and get user confirmation.

**BA-to-SA type mapping reference:**

| BA data_type (EntityAttribute) | Suggested SA data_type (DomainAttribute) | Notes |
|-------------------------------|------------------------------------------|-------|
| Текст | String | Default text type |
| Число | Int or Decimal | Ask user: integer or decimal? |
| Дата | Date or DateTime | Ask user: date only or with time? |
| Ссылка | Reference | Becomes a RELATES_TO edge |
| Перечисление | Enum | Create Enumeration node |
| Логическое | Boolean | |

**Present for each entity:**

```
**Import BA entity: {BA name} ({BA id})**

BA attributes (business types):
| # | BA Attribute | BA Type     | Proposed SA Name | Proposed SA Type | Nullable |
|---|-------------|-------------|------------------|-----------------|----------|
| 0 | (auto)      | --          | id               | UUID            | false    |
| 1 | {ba_attr}   | {ba_type}   | {sa_name}        | {sa_type}       | {t/f}    |

Proposed SA entity:
- SA name: {EnglishName} (e.g. "Order", "Customer")
- SA id: DE-{EnglishName}
- Module: {module}

Questions:
1. Are the proposed SA types correct?
2. Add or remove attributes?
3. Which module should this entity belong to?
```

**Rules for SA name generation:**
- Use English PascalCase: "Заказ" -> "Order", "Позиция заказа" -> "OrderItem"
- Always add `id` attribute (UUID, not nullable) as the first attribute
- Convert "Ссылка" attributes to relationship edges (do not create as DomainAttribute)
- Convert "Перечисление" attributes to Enum type (will create Enumeration node in Step 3)

---

### Step 3: Create nodes and attributes in Neo4j

**Goal:** Create DomainEntity, DomainAttribute, and Enumeration nodes.

**Pre-check -- get next available DomainAttribute IDs:**

```cypher
// next_domain_attribute_id
MATCH (da:DomainAttribute)
WHERE da.id STARTS WITH $prefix
WITH max(toInteger(split(da.id, '-A')[1])) AS maxNum
RETURN $prefix + '-A' + 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 DomainEntity:**

```cypher
// create_domain_entity
MERGE (de:DomainEntity {id: $id})
SET de.name = $name,
    de.module = $module,
    de.description = $description,
    de.shared = $shared
```

Parameters:
- `$id` -- format `DE-{EnglishName}` (e.g. "DE-Order")
- `$name` -- English PascalCase (e.g. "Order")
- `$module` -- module name (e.g. "orders")
- `$description` -- Russian description from BA entity
- `$shared` -- boolean. `true` if this entity is intentionally referenced from multiple modules (typical for User, Group, GroupLecture, organization-wide aggregate roots); `false` for module-local entities. Used by validator L6.1 to skip cross-module attribute consistency checks on intentionally-shared entities. If forgotten, `nacl-sa-flags backfill-all` defaults it to `false`; refine selectively via `/nacl-sa-flags set-shared --entity  true`.

**Cypher -- link entity to module:**

```cypher
// link_entity_to_module
MATCH (m:Module {name: $moduleName}), (de:DomainEntity {id: $entityId})
MERGE (m)-[:CONTAINS_ENTITY]->(de)
```

**Cypher -- create DomainAttribute:**

```cypher
// create_domain_attribute
MERGE (da:DomainAttribute {id: $id})
SET da.name = $name,
    da.data_type = $dataType,
    da.nullable = $nullable,
    da.description = $description,
    da.internal = $internal
```

Parameters:
- `$id` -- format `{EntityName}-A{NN}` (e.g. "Order-A01")
- `$name` -- camelCase attribute name (e.g. "orderNumber")
- `$dataType` -- one of: UUID, String, Int, Decimal, Boolean, Date, DateTime, Enum, JSON, Reference
- `$nullable` -- boolean
- `$description` -- Russian description
- `$internal` -- boolean. `true` for system attributes that should never appear in any user form (surrogate keys, foreign keys, timestamps, password hashes, refresh tokens, telemetry IDs, third-party-system internal references). `false` for user-facing attributes. Used by validator L4.2 to skip "attribute not referenced by any FormField" checks on internal attributes. If forgotten, `nacl-sa-flags backfill-all --detect-internal` will auto-flag attributes whose names match common system patterns (`id`, `*_id`, `*_at`, `*_token`, `*_hash`); the user reviews and refines the rest.

**Cypher -- link attribute to entity:**

```cypher
// link_attribute_to_entity
MATCH (de:DomainEntity {id: $entityId}), (da:DomainAttribute {id: $attrId})
MERGE (de)-[:HAS_ATTRIBUTE]->(da)
```

**Cypher -- create Enumeration with values:**

```cypher
// create_enumeration
MERGE (en:Enumeration {id: $id})
SET en.name = $name,
    en.description = $description
```

Parameters:
- `$id` -- format `ENUM-{Name}` (e.g. "ENUM-OrderStatus")
- `$name` -- English PascalCase (e.g. "OrderStatus")

**Cypher -- create EnumValue:**

```cypher
// create_enum_value
MERGE (ev:EnumValue {id: $id})
SET ev.value = $value,
    ev.description = $description
```

Parameters:
- `$id` -- format `{ENUM_ID}-V{NN}` (e.g. "ENUM-OrderStatus-V01")

**Cypher -- link enum to entity and values:**

```cypher
// link_enum_to_entity
MATCH (de:DomainEntity {id: $entityId}), (en:Enumeration {id: $enumId})
MERGE (de)-[:HAS_ENUM]->(en)
```

```cypher
// link_enum_value
MATCH (en:Enumeration {id: $enumId}), (ev:EnumValue {id: $valueId})
MERGE (en)-[:HAS_VALUE]->(ev)
```

**If BA entity has states (EntityState)**, create a corresponding Enumeration from them:
1. Map each EntityState.name to an EnumValue (uppercase, underscored: "Новый" -> "NEW")
2. Create Enumeration node
3. Link to entity via HAS_ENUM
4. Add the status DomainAttribute with data_type: "Enum"

---

### Step 3.5: Auto-create Enumerations from BA EntityStates

**Goal:** When a BA entity has EntityState nodes AND the corresponding DomainEntity has an attribute with `data_type: "Enum"` (typically `status`), automatically create Enumeration and EnumValue nodes.

This step runs after attribute creation (Step 3) and before relationship/handoff edges (Step 4).

**Cypher -- find BA states for a source entity:**

```cypher
// ba_entity_states_for_enum
MATCH (be:BusinessEntity {id: $baEntityId})-[:HAS_STATE]->(es:EntityState)
RETURN es.name ORDER BY es.id
```

**Cypher -- check if DomainEntity has an Enum attribute without an Enumeration:**

```cypher
// enum_attr_without_enumeration
MATCH (de:DomainEntity {id: $entityId})-[:HAS_ATTRIBUTE]->(da:DomainAttribute {data_type: "Enum"})
WHERE NOT (de)-[:HAS_ENUM]->(:Enumeration)
RETURN da.id AS attr_id, da.name AS attr_name
```

**If both queries return results**, create the Enumeration automatically:

1. Create Enumeration node:
```cypher
MERGE (e:Enumeration {id: "ENUM-" + $entityName + "Status"})
SET e.name = $entityName + "Status",
    e.description = "Auto-generated from BA EntityStates for " + $entityName
```

2. For each EntityState, create an EnumValue node:
```cypher
MERGE (ev:EnumValue {id: "ENUM-" + $entityName + "Status-V" + $seqNum})
SET ev.value = $stateName,
    ev.label = $stateName
```

3. Link values to enumeration and enumeration to entity:
```cypher
MATCH (e:Enumeration {id: $enumId}), (ev:EnumValue {id: $valueId})
MERGE (e)-[:HAS_VALUE]->(ev)
```

```cypher
MATCH (de:DomainEntity {id: $entityId}), (e:Enumeration {id: $enumId})
MERGE (de)-[:HAS_ENUM]->(e)
```

**Example:** BA entity `OBJ-UAZ-01` has EntityStates `Новый`, `Подтверждён`, `Отгружен`. DomainEntity `DE-SparePartList` has attribute `status` with `data_type: "Enum"`. This step creates:
- `Enumeration {id: "ENUM-SparePartListStatus", name: "SparePartListStatus"}`
- `EnumValue {id: "ENUM-SparePartListStatus-V01", value: "Новый", label: "Новый"}`
- `EnumValue {id: "ENUM-SparePartListStatus-V02", value: "Подтверждён", label: "Подтверждён"}`
- `EnumValue {id: "ENUM-SparePartListStatus-V03", value: "Отгружен", label: "Отгружен"}`
- Edges: `(DE-SparePartList)-[:HAS_ENUM]->(ENUM-SparePartListStatus)`, `(ENUM-SparePartListStatus)-[:HAS_VALUE]->(each EV)`

---

### Step 4: Create handoff and relationship edges

**Goal:** Create BA->SA traceability edges and inter-entity relationships.

**Cypher -- create REALIZED_AS handoff (BusinessEntity -> DomainEntity):**

```cypher
// handoff_realized_as
MATCH (be:BusinessEntity {id: $baEntityId}), (de:DomainEntity {id: $saEntityId})
MERGE (be)-[:REALIZED_AS]->(de)
```

**Cypher -- create TYPED_AS handoff (EntityAttribute -> DomainAttribute):**

```cypher
// handoff_typed_as
MATCH (ea:EntityAttribute {id: $baAttrId}), (da:DomainAttribute {id: $saAttrId})
MERGE (ea)-[:TYPED_AS]->(da)
```

**Cypher -- create RELATES_TO between DomainEntities:**

Inherit relationships from BA layer. Map BA rel_type/cardinality to SA equivalents:

| BA rel_type | SA rel_type |
|-------------|-------------|
| агрегация | composition |
| ассоциация | association |
| зависимость | dependency |

```cypher
// create_entity_relationship
MATCH (de1:DomainEntity {id: $sourceId}), (de2:DomainEntity {id: $targetId})
MERGE (de1)-[:RELATES_TO {rel_type: $relType, cardinality: $cardinality}]->(de2)
```

Parameters:
- `$sourceId`, `$targetId` -- DomainEntity IDs
- `$relType` -- "composition", "association", "dependency"
- `$cardinality` -- "1:1", "1:N", "N:1", "N:M"

**To find which BA relationships to inherit:**

```cypher
// ba_relationships_for_import
MATCH (be1:BusinessEntity)-[r:RELATES_TO]->(be2:BusinessEntity)
MATCH (be1)-[:REALIZED_AS]->(de1:DomainEntity)
MATCH (be2)-[:REALIZED_AS]->(de2:DomainEntity)
WHERE NOT (de1)-[:RELATES_TO]->(de2)
RETURN be1.id AS ba_source, be2.id AS ba_target,
       de1.id AS sa_source, de2.id AS sa_target,
       r.rel_type AS ba_rel_type, r.cardinality AS cardinality
```

**After all edges are created, verify with coverage stats:**

```cypher
// handoff_coverage_stats (from handoff-queries.cypher)
MATCH (be:BusinessEntity {type: "Бизнес-объект"})
WITH count(be) AS total_entities
OPTIONAL MATCH (be2:BusinessEntity {type: "Бизнес-объект"})-[:REALIZED_AS]->(:DomainEntity)
WITH total_entities, count(be2) AS covered_entities
RETURN total_entities, covered_entities,
       CASE WHEN total_entities > 0
            THEN round(100.0 * covered_entities / total_entities)
            ELSE 0 END AS coverage_pct
```

**Present final summary:**

```
**IMPORT_BA complete**

Created:
- DomainEntity nodes: {count}
- DomainAttribute nodes: {count}
- Enumeration nodes: {count}
- EnumValue nodes: {count}

Handoff edges:
- REALIZED_AS (B

…

## 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-domain
- 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%.
