skip to content

The graph model

There is exactly one graph, and exactly one node notion. Not one per framework, not one per kind — one, full stop, and there is no second node notion anywhere in the substrate.

A node is a position with attributes and incident labelled edges: an identity, a set of attributes — some declared, some computed — and the labelled edges touching it. That’s the whole notion. A “kind” is not a second one: a kind is a schema-level declared datum carried by a node, and nothing in the substrate reads a kind to decide what a node is. aspect and entity are gen-declared kinds like any other a framework might declare — the substrate treats them exactly the way it treats a kind it’s never seen before.

An edge carries a label and nothing else. No interpreted payload rides on an edge, which is what forces reification: any relation that needs to carry content can’t be represented as an edge, so it has to be a node instead — a binding node, a reified relation identified by which relation it is and what it relates:

identity = hashIdentity <relation-kind> { <relatum label> = <that relatum's identity>; … }

Each relatum contributes its own derived identity to that hash, never its bare name, so a binding node’s identity is not a function of what anything happens to be called. The relation kind is required, and an empty one is refused by name rather than silently collapsing two different relations that happen to share relata into one node. Minting is staged, too: a binding may only relate nodes minted in a strictly earlier pass, so the identity computation can never need its own result to proceed.

flowchart LR
  a["Node A"] -- "label" --> b["Node B"]
  a -.-> r["Binding node\nidentity = hashIdentity(kind, relata)"]
  b -.-> r
  c["Node C"] -- "label" --> r

Every node either carries an identifier, or it cannot be an edge endpoint. Identity-bearing kinds additionally carry a derived identity, and there is exactly one authority that mints it: hashIdentity. Nothing else in the substrate invents one. Where an identity can’t be minted totally — an ordinary kind derives its identity keys reflected from its own options, never from a separately maintained declaration — the substrate refuses rather than guessing. A missing identity is a refusal, not a silently invented placeholder.

The graph is the configuration, not a staging area you build once and then read from. Every derived view is a named materialized query result — a projection has a name and a defining query, or it is not a view. Content movement between nodes is the same idea from two ends: a collector is a receiver-rooted query, a broadcaster is a producer’s declaration paired with a standing query. Neither one copies content around by hand — both are queries against the one graph, and the graph doesn’t change shape to answer them.

The reference-attribute-grammar literature is where the mechanism for keeping two relations — declared and dynamic — coherent over one structure comes from; see Hedin (2000) if you want the theory underneath this.

palette
dark
light
↑↓ select apply esc close

Palettes adapted from Catppuccin (Macchiato) (MIT), Tokyo Night (Apache-2.0), gruvbox (MIT), Catppuccin (Latte) (MIT), Rosé Pine (Dawn) (MIT).