Install
$ agentstack add skill-itsalt-nacl-nacl-sa-domain ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →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:
// 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:
// 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:
// 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):
// 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):
// 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 datatype (EntityAttribute) | Suggested SA datatype (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
idattribute (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:
// 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:
// create_domain_entity
MERGE (de:DomainEntity {id: $id})
SET de.name = $name,
de.module = $module,
de.description = $description,
de.shared = $shared
Parameters:
$id-- formatDE-{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.trueif this entity is intentionally referenced from multiple modules (typical for User, Group, GroupLecture, organization-wide aggregate roots);falsefor 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-alldefaults it tofalse; refine selectively via/nacl-sa-flags set-shared --entity true.
Cypher -- link entity to module:
// link_entity_to_module
MATCH (m:Module {name: $moduleName}), (de:DomainEntity {id: $entityId})
MERGE (m)-[:CONTAINS_ENTITY]->(de)
Cypher -- create DomainAttribute:
// 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.truefor 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).falsefor 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-internalwill 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:
// link_attribute_to_entity
MATCH (de:DomainEntity {id: $entityId}), (da:DomainAttribute {id: $attrId})
MERGE (de)-[:HAS_ATTRIBUTE]->(da)
Cypher -- create Enumeration with values:
// create_enumeration
MERGE (en:Enumeration {id: $id})
SET en.name = $name,
en.description = $description
Parameters:
$id-- formatENUM-{Name}(e.g. "ENUM-OrderStatus")$name-- English PascalCase (e.g. "OrderStatus")
Cypher -- create EnumValue:
// 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:
// link_enum_to_entity
MATCH (de:DomainEntity {id: $entityId}), (en:Enumeration {id: $enumId})
MERGE (de)-[:HAS_ENUM]->(en)
// 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:
- Map each EntityState.name to an EnumValue (uppercase, underscored: "Новый" -> "NEW")
- Create Enumeration node
- Link to entity via HAS_ENUM
- 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:
// 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:
// 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:
- Create Enumeration node:
MERGE (e:Enumeration {id: "ENUM-" + $entityName + "Status"})
SET e.name = $entityName + "Status",
e.description = "Auto-generated from BA EntityStates for " + $entityName
- For each EntityState, create an EnumValue node:
MERGE (ev:EnumValue {id: "ENUM-" + $entityName + "Status-V" + $seqNum})
SET ev.value = $stateName,
ev.label = $stateName
- Link values to enumeration and enumeration to entity:
MATCH (e:Enumeration {id: $enumId}), (ev:EnumValue {id: $valueId})
MERGE (e)-[:HAS_VALUE]->(ev)
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):
// handoff_realized_as
MATCH (be:BusinessEntity {id: $baEntityId}), (de:DomainEntity {id: $saEntityId})
MERGE (be)-[:REALIZED_AS]->(de)
Cypher -- create TYPED_AS handoff (EntityAttribute -> DomainAttribute):
// 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 reltype | SA reltype | |-------------|-------------| | агрегация | composition | | ассоциация | association | | зависимость | dependency |
// 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:
// 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:
// 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.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.