Packages

Arrows JSON ↔ Cypher — knowledge graph fragments made in relationship

Current section

Files

Jump to
artefact usage-rules.md
Raw

usage-rules.md

<!--
SPDX-FileCopyrightText: 2026 artefactory contributors <https://github.com/diffo-dev/artefactory/graphs/contributors>
SPDX-License-Identifier: MIT
-->
# Rules for working with Artefact
## Installation
The preferred way to add Artefact to a project is via Igniter:
```bash
mix igniter.install artefact
```
This wires up the formatter automatically. If your project does not use Igniter, add the dep manually and run `mix deps.get`.
## What Artefact is
Artefact is an Elixir library for building, combining, and persisting knowledge graph fragments. An `%Artefact{}` is a named, typed property graph — a small, self-contained piece of knowledge. It is not an application data model, a database schema, or a general-purpose graph library.
The intended use is knowledge memorialisation: capturing shared understanding between people, agents, or systems in a form that can be combined, versioned, and persisted.
## require Artefact
`Artefact.new/1`, `Artefact.new!/1`, `Artefact.combine!/2`, and all other operations are **macros**. Always `require Artefact` before calling them:
```elixir
require Artefact
artefact = Artefact.new!(
title: "Us Two",
nodes: [
matt: [labels: ["Agent"], properties: %{"name" => "Matt"}],
claude: [labels: ["Agent"], properties: %{"name" => "Claude"}]
],
relationships: [
[from: :matt, type: "US_TWO", to: :claude]
]
)
```
Without `require`, you will get a compile-time error about undefined functions. This is the most common source of confusion when first using Artefact.
## UUID is identity
Every node carries a UUIDv7 `uuid`. This is its identity — the same UUID in two artefacts means the same node. `combine!/2` uses UUID equality to find shared nodes and merge them.
**Never change a UUID once it has been used in a persisted or shared artefact.** If you assign a UUID explicitly at construction time, keep it. If you do not assign one, Artefact generates a time-ordered UUIDv7 automatically.
For importing from external sources — Mermaid diagrams, Cypher files, JSON — derive UUIDs deterministically from a stable identifier using `Artefact.UUID.from_name/1`:
```elixir
uuid = Artefact.UUID.from_name("std_ulogic")
# same name always → same UUID, valid UUIDv7
```
This means the same external id imported twice always produces the same node, and `combine!/2` will bind correctly across imports.
## Operations at a glance
| Operation | What it does | Key constraint |
|---|---|---|
| `new!/1` | Build a fresh artefact | Macro — `require Artefact` |
| `combine!/2` | Union two artefacts via shared UUIDs | Different `base_label` required |
| `harmonise!/3` | Union via explicit bindings | Different `base_label` required |
| `compose!/2` | Concatenate — nodes stay disjoint | No shared UUIDs expected |
| `graft!/2` | Extend an existing artefact inline | Every new node must carry `:uuid` |
Each operation has a `!/n` (raises) and `/n` (returns `{:ok, _} | {:error, _}`) variant.
## combine!/2 requires different base_label values
`combine!/2` finds shared nodes automatically by UUID. It requires the two artefacts to have **different** `base_label` values — this distinguishes "what I know" from "what I am adding":
```elixir
# Raises Artefact.Error.Operation with same_base_label
Artefact.combine!(a, b) # both default base_label to calling module name
# Correct
a = Artefact.new!(base_label: "Signals", ...)
b = Artefact.new!(base_label: "Values", ...)
combined = Artefact.combine!(a, b)
```
When no `base_label` is set explicitly, it defaults to the short name of the calling module — so two artefacts built in the same module will clash.
## Mermaid import and export
`Artefact.Mermaid.export/2` converts an artefact to a Mermaid `graph` source string. `Artefact.Mermaid.from_mmd!/2` parses it back. The round-trip is lossless for: title, description, node names, labels, and relationship types.
### UUID identity anchors on the Mermaid node id
When importing with `from_mmd!/2`, the UUID of each node is derived from its **Mermaid node id** — the `\w+` identifier (e.g. `val_0`, `std_ulogic`) — not the display label inside the shape. Keep node ids stable across diagram versions; changing an id changes the UUID and breaks bindings.
```mermaid
graph LR
std_ulogic(("std_ulogic<br/>Signal")) ← id is std_ulogic, UUID derived from "std_ulogic"
```
### Declare nodes separately from edges for label recovery
When a node's shape is declared inline on an edge line, the label is not captured:
```
val_0["VALUE · 0"] -->|ENUMERATES| value ← label "VALUE" is lost
```
Use a separate declaration line:
```
graph LR
val_0["VALUE · 0"]
val_0 -->|ENUMERATES| value
```
The export format produced by `export/2` always uses separate lines, so this only applies to hand-authored Mermaid.
### Node label conventions in Mermaid
Two formats are recognised inside node shapes:
- `name<br/>Label1 Label2` — our export format
- `LABEL · name` — yarn convention (one label and name separated by ` · `)
### Node descriptions via click tooltips
Node `description` properties are exported as `click id "text"` lines and recovered on import. They are visible as hover tooltips in Mermaid renderers.
## %Artefact{} struct shape
```elixir
%Artefact{
uuid: "019e...", # UUIDv7 — the artefact's own identity
title: "My Artefact", # optional
description: "...", # optional
base_label: "Concept", # optional — collapsed into per-node labels at export
graph: %Artefact.Graph{
nodes: [
%Artefact.Node{
id: "n0", # internal sequential id — do not rely on this across artefacts
uuid: "019e...", # UUIDv7 — stable identity
labels: ["Concept", "Thing"],
properties: %{"name" => "Alpha", "description" => "..."}
}
],
relationships: [
%Artefact.Relationship{
id: "r0", # internal sequential id
type: "RELATES", # MACRO_CASE convention
from_id: "n0",
to_id: "n1",
properties: %{}
}
]
}
}
```
Node `id` values (`n0`, `r0`) are internal and sequential within one artefact. Use `uuid` for stable cross-artefact identity.