Artifact Relations Graph
Model Forge artifacts form a typed graph across all artifact types — not just Elements. Schemas reference other schemas, mappings connect source and target Elements, pipelines wire sources, mappings and sinks, DataSources and DataSinks bind to the Element that describes their payload, and DataSets and DataStructures aggregate their members. Every edge is recorded durably and fed into the in-memory dependency graph (ADR 054).
Edge types
Each relation an artifact declares is stored as a typed edge:
| Relation source | Stored edge type | Meaning |
|---|---|---|
JSON Schema $ref inside an Element | schema-ref | Element depends on another Element |
XSD xs:import | xsd-import | XSD-backed Element imports another Element namespace |
Element x-core-ref association target | association-ref | Element references another Element by a concrete association (foreign-key) target |
Mapping source | mapping-source | Mapping reads from a source Element |
Mapping target | mapping-target | Mapping writes to a target Element |
| Pipeline nodes | pipeline-node | Pipeline uses a DataSource, Mapping or DataSink |
DataSet *Refs arrays | dataset-ref | DataSet contains or references member artifacts; reference_name carries the member kind (datastructure, mapping, pipeline, datasource, datasink) |
DataSource element field | datasource-element | The DataSource's own payload is described by an Element |
DataSink element field | datasink-element | The DataSink's own payload is described by an Element |
DataStructure $defs.*.$ref | datastructure-ref | DataStructure groups member Elements |
Note the distinction between the two DataSource/DataSink edge families. A
DataSet → member edge from a DataSet manifest's *Refs arrays is always
dataset-ref, whatever the member is; the member kind goes into
reference_name. The deletion policy depends on that uniform typing, because it
counts the DataSet memberships of an artifact separately from its other
references. datasource-element / datasink-element are a different thing: the
DataSource's / DataSink's own binding to the Element that describes its
payload.
The durable source is artifact_reference, described in
PostgreSQL Artifact Registry.
The table stores complete per-version edges, including cycles and unresolved
targets. It preserves the authored target URN verbatim, including pinned versions
and :latest.
The embedded facade exposes these relations through:
dependencies/dependentsreturn an artifact's direct graph neighbours (version-precise).mapsTo/mappedFromreturn the Mappings an Element participates in.- A Mapping document names its
sourceandtargetElements; a Pipeline document references its DataSources, DataSinks and Mappings; a DataSet manifest lists its members by URN.
dependencies / dependents answer for any artifact URN, over all the
edge types above — this is what the admin UI's graph view renders. The same
typed edges are also returned as the dependency lists of every write result
(ArtifactWriteResult.dependencies), so a host can mirror them without a
separate query.
Composition and delete protection
The edge types above arrange the artifact types into a composition DAG.
Some artifacts act as containers that reference the artifacts they compose or
consume; the referenced artifacts are their contents. Read top-down: a DataSet
composes Pipelines, Mappings, DataSources, DataSinks and Elements; a Pipeline
wires DataSources, Mappings and DataSinks; a Mapping binds a source and a target;
a DataSource and a DataSink each bind the Element that describes their payload;
a DataStructure groups its member Elements; and an Element may $ref further
Elements.
An arrow A → B means A references B, so B is a content of the container A.
The integrity rule follows directly from this DAG. Keeping the model internally consistent is Model Forge's core job, so it never lets a reference dangle:
- A container may always be deleted. Deleting it removes only the container, never its contents; nothing it points at can block it (its own outgoing edges never protect it).
- A content artifact cannot be deleted while any container still references
it.
deleteArtifactthrowsArtifactInUseExceptionand lists every blocking dependent. To remove the artifact, first update or delete the referencing container(s). - Only an artifact's own self-references (cycles inside a single document) are exempt — a document cannot block its own deletion.
Put differently: a delete is blocked by incoming edges, never by outgoing
ones, and every edge type blocks — a grouping edge (datastructure-ref)
protects its member exactly like a hard schema-ref dependency does.
| Deleting… | …is blocked while it is referenced by | via edge |
|---|---|---|
| Element | a DataStructure, Mapping, DataSource, DataSink, DataSet, or another Element | datastructure-ref, mapping-source/mapping-target, datasource-element, datasink-element, dataset-ref, schema-ref/xsd-import/association-ref |
| DataStructure | a Mapping that reads or writes it | mapping-source / mapping-target |
| DataSource / DataSink | a Pipeline node or a DataSet | pipeline-node, dataset-ref |
| Mapping | a Pipeline node or a DataSet | pipeline-node, dataset-ref |
| Pipeline | a DataSet that composes it | dataset-ref |
| DataSet | nothing references a DataSet — it is always deletable | — |
Cascading delete
deleteArtifact takes an optional cascade flag. With cascade=true the target's members
(the artifacts it references) are deleted along with it — but each member only if, once this
container is gone, no other artifact still references it. A member that another container still
groups, or that a sibling references, is kept. The cascade applies the same rule recursively, so
deleting a DataSet can remove its Pipelines, Mappings, DataSources and Elements down the tree,
stopping wherever something is still in use (mutually-referencing members keep each other).
The target itself still obeys the base rule — cascade only ever deletes downward into members, never a target that something else references. Each artifact is deleted in its own transaction and re-checked against the live registry, so a cascade can never leave a dangling reference even though it is not a single atomic operation.