Artefact

Module VersionHex DocsLicenseREUSE statusAsk DeepWikiRun in Livebook

Artefacts (note the Australian spelling) are made objects. In an Artefactory they represent a fragment of knowledge — typically abstract, insightful, contextual.

An %Artefact{} is a small, self-contained property graph: nodes with labels and properties, connected by typed directed relationships. The canonical form is the Elixir struct. Arrows JSON and Cypher are derived representations — JSON for interchange and visual editing with Arrows.app, Cypher for persistence in Neo4j.

As we yarn we naturally exchange and create Artefacts.

Installation

The preferred way to install Artefact is via Igniter:

mix igniter.install artefact

Or add the dependency manually:

def deps do
  [
    {:artefact, "~> 0.3"}
  ]
end

Building an Artefact

require Artefact

matt  = %Artefact.Node{
  id: "n0", uuid: "019da897-f2de-77ca-b5a4-40f0c3730943",
  labels: ["Agent", "Me"],
  properties: %{"name" => "Matt"}
}

claude = %Artefact.Node{
  id: "n1", uuid: "019da897-f2de-768c-94e2-3005f2431f37",
  labels: ["Agent", "You"],
  properties: %{"name" => "Claude"}
}

us_two = Artefact.new!(
  title: "UsTwo",
  base_label: "UsTwo",
  graph: %Artefact.Graph{
    nodes: [matt, claude],
    relationships: [
      %Artefact.Relationship{
        id: "r0", from_id: "n0", to_id: "n1",
        type: "US_TWO", properties: %{}
      }
    ]
  }
)

The base_label is a watermark applied to every node at output time — it identifies which artefact a node belongs to without polluting the struct itself.

Exporting

# Cypher — MERGE upserts by uuid identity, CREATE always makes new nodes
Artefact.Cypher.merge(us_two)
Artefact.Cypher.create(us_two)

# Parameterised Cypher for driver use (e.g. Bolty)
{cypher, params} = Artefact.Cypher.merge_params(us_two)

# Arrows JSON — for round-trip with Arrows.app
Artefact.Arrows.to_json(us_two)

Combining and Extending Artefacts

Operations come in two variants: op/n returns {:ok, %Artefact{}} | {:error, error}; op!/n returns the artefact directly or raises the error struct. Use ! in pipelines or when you'd rather let exceptions propagate; use the non-! form when you want to handle errors explicitly.

# compose — disjoint union, nodes remain independent
{:ok, combined} = Artefact.compose(a1, a2)

# combine — pipeline-friendly union; bindings auto-found via shared uuid.
# Returns {:error, %Artefact.Error.Operation{tag: :no_shared_bindings}}
# if heart and other share no node uuids.
result =
  my_knowing
  |> Artefact.combine!(my_valuing)
  |> Artefact.combine!(my_being)
  |> Artefact.combine!(my_doing, title: "MeMind", description: "Mind of Me")

# harmonise — union via declared bindings.
# Lower uuid wins identity, labels are unioned, left wins on property conflict.
{:ok, bindings} = Artefact.Binding.find(a1, a2)
{:ok, harmonised} = Artefact.harmonise(a1, a2, bindings)

# graft — extend an existing artefact inline with new nodes and
# relationships. args matches Artefact.new's inline shape, but every
# node MUST carry :uuid (no auto-find — uuid is the binding).
# Nodes whose uuid lives in left bind to it (labels unioned, properties
# merged left-wins). Nodes with new uuids are added.
result =
  me_mind
  |> Artefact.graft!(
       [
         nodes: [
           {:me,          [uuid: "019ddb71-c70b-7b3e-83b1-58f4d0be2852"]},
           {:stewardship, [labels: ["Knowing"],
                           uuid: "019df318-698c-77d6-bc7b-ea041a019a7f"]}
         ],
         relationships: [[from: :me, type: "KNOWING", to: :stewardship]]
       ],
       title: "MeMind + Stewardship",
       description: "Stewardship grafted onto MeMind."
     )

Errors are Splode-typed structs — pattern-match on Artefact.Error.Invalid (validation-rule violations) or Artefact.Error.Operation (op-specific outcomes) to handle each case. See MIGRATION.md for the full error shape table.

Provenance is recorded automatically — every artefact carries metadata describing how it was created, including the calling module and, for derived artefacts, a summary of each source.

Validation

Artefact.is_artefact?(value)         # boolean
Artefact.is_valid?(artefact)         # boolean
Artefact.validate(artefact)          # :ok | {:error, %Artefact.Error.Invalid{reasons: [...]}}
Artefact.validate!(artefact)         # :ok | raises Artefact.Error.Invalid

Every operation validates its inputs and the produced artefact, so corruption fails at the call site rather than steps downstream.

Importing from Arrows JSON

artefact = Artefact.Arrows.from_json!(json, diagram: "path/to/source.json")

Acknowledgements

Artefactory is inspired by Indigenous Systems Thinking and the profound wisdom presented by Tyson Yunkaporta in Sand Talk, grounded in countless years of sustainable, harmonious living.

License

MIT — see LICENSES/MIT.txt.