Skip to main content
Version: V2-Next

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 sourceStored edge typeMeaning
JSON Schema $ref inside an Elementschema-refElement depends on another Element
XSD xs:importxsd-importXSD-backed Element imports another Element namespace
Element x-core-ref association targetassociation-refElement references another Element by a concrete association (foreign-key) target
Mapping sourcemapping-sourceMapping reads from a source Element
Mapping targetmapping-targetMapping writes to a target Element
Pipeline nodespipeline-nodePipeline uses a DataSource, Mapping or DataSink
DataSet *Refs arraysdataset-refDataSet contains or references member artifacts; reference_name carries the member kind (datastructure, mapping, pipeline, datasource, datasink)
DataSource element fielddatasource-elementThe DataSource's own payload is described by an Element
DataSink element fielddatasink-elementThe DataSink's own payload is described by an Element
DataStructure $defs.*.$refdatastructure-refDataStructure 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 / dependents return an artifact's direct graph neighbours (version-precise).
  • mapsTo / mappedFrom return the Mappings an Element participates in.
  • A Mapping document names its source and target Elements; 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. deleteArtifact throws ArtifactInUseException and 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 byvia edge
Elementa DataStructure, Mapping, DataSource, DataSink, DataSet, or another Elementdatastructure-ref, mapping-source/mapping-target, datasource-element, datasink-element, dataset-ref, schema-ref/xsd-import/association-ref
DataStructurea Mapping that reads or writes itmapping-source / mapping-target
DataSource / DataSinka Pipeline node or a DataSetpipeline-node, dataset-ref
Mappinga Pipeline node or a DataSetpipeline-node, dataset-ref
Pipelinea DataSet that composes itdataset-ref
DataSetnothing 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.