Skip to main content
Version: V2-Next

Data Modelling

Model Forge manages domain data structures, their relationships, versions, dependencies and transformations. It uses a PostgreSQL-backed artifact registry (owned by Model Forge) as the persistent artifact store and builds a semantic model layer on top of it: the registry does not manage files as primary objects, but domain model artifacts that are represented as JSON Schema.

JSON Schema as the canonical model​

Every Element is modelled as a standalone JSON Schema 2020-12 document (ADR 048):

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:core:platform:civitas:element:common:WeatherObservation:6niukwegew:1.0.0",
"title": "WeatherObservation",
"type": "object",
"properties": {
"sensorId": { "type": "string" },
"location": { "$ref": "urn:core:platform:civitas:element:common:GeoPoint:3ak90vqrog:1.0.0" }
}
}

Model Forge does not introduce a separate attribute, class or slot metamodel. The domain truth lives in the JSON Schema itself.

Persistence model​

Every Element is stored as its own artifact row (with one artifact_version per version), keyed by the logical URN (without version). Versions are backend-owned SemVer; the version is simultaneously the last segment of the versioned URN:

FieldValue
artifact typeelement (the authored format jsonschema/xsd is a per-version representation, not part of the type)
logical URNurn:core:platform:civitas:element:common:WeatherObservation:6niukwegew
versions1.0.0, 1.1.0, 2.0.0, …

Updates request a SemVer bump via the versionBump field (PATCH / MINOR / MAJOR) on the save command; the backend assigns the next version and never overwrites an older one (ADR 057). Model Forge does not perform schema-compatibility checks.

References between schemas​

Structural composition — embedding one Element's shape into another — is modelled exclusively through standard JSON Schema mechanisms — $ref, allOf, oneOf, anyOf, $defs; no CORE-specific extensions are required. A by-reference link (a foreign key by URN) instead uses the x-core-ref annotation, described at the end of this section.

Inheritance and extension: allOf​

{
"allOf": [
{ "$ref": "urn:core:platform:civitas:element:common:ObservationBase:7zlmk0utgy:1.0.0" },
{
"type": "object",
"properties": {
"temperature": { "type": "number" }
}
}
]
}

Polymorphism: oneOf​

{
"oneOf": [
{ "$ref": "urn:core:platform:civitas:element:common:MqttSource:wajrvc0l4y:1.0.0" },
{ "$ref": "urn:core:platform:civitas:element:common:HttpSource:wun2fsrg3b:1.0.0" }
]
}

Combinable variants: anyOf​

{
"anyOf": [
{ "$ref": "urn:core:platform:civitas:element:common:TemperatureSensor:6tdxa8zu0m:1.0.0" },
{ "$ref": "urn:core:platform:civitas:element:common:HumiditySensor:qignai4t87:1.0.0" }
]
}

Each external $ref becomes an edge in the dependency graph and a stored artifact reference (a row in artifact_reference); the full graph, including cycles, is stored — see ADR 056. This enables reference validation, dependency analysis, impact analysis and the generated views.

Reference by URN (foreign key): x-core-ref​

The mechanisms above ($ref, allOf, oneOf, anyOf) embed the target — composition: the value is an instance shaped like the referenced schema (inlined only when an inlined or bundled view is requested; the default read keeps the raw $ref URN). To link an Element to another without embedding it, a plain string field carries the target's CORE URN and is marked with x-core-ref — a foreign key (UML association): nothing is embedded, the target stays an independent artifact, and its existence is checked against the registry on import. It is the by-reference counterpart to $ref's by-value embedding.

{
"stehtAn": {
"type": "string",
"pattern": "^urn:",
"x-core-ref": { "type": "urn:core:platform:civitas:element:common:Strasse:u8pwgr2zzg:1.0.0" }
}
}

Modelling constructs at a glance​

The everyday modelling constructs map onto JSON Schema (and thus CORE-IR) as follows. Cardinalities first:

CardinalityMeaningJSON Schema mapping
1..1required single valuelisted in required, scalar schema
0..1optional single valuenot in required, scalar schema
1..*required, one or morein required, type: array with minItems: 1
0..*optional, zero or morenot in required, type: array
m..nboundedtype: array with minItems: m, maxItems: n

And the constructs themselves:

ConstructJSON Schema / CORE-IR mapping
Class / entitytype: object document; $id = versioned CORE URN; class name = title
Attributeentry under properties; type → JSON Schema type + validation keywords
Primitive typestring (pattern, format, minLength/maxLength), integer/number (minimum/maximum/exclusiveMinimum/multipleOf), boolean
Composition (embed by value)property whose value is a $ref to the target URN — e.g. { "$ref": "urn:core:platform:civitas:element:common:GeoPoint:3ak90vqrog:1.0.0" } — or an array of such $refs for * cardinalities
Association (reference by URN)property that is a URN string carrying x-core-ref (foreign key), or an array of such strings for *
Inheritance / extensionallOf: [ { "$ref": "<superclass-urn>" }, { "type": "object", "properties": … } ]
Enumeration{ "type": "string", "enum": [ … ] }, inline on the property or as a referenced Element

Composition ($ref) and association (x-core-ref) are the two sides of the same choice — embed the target's shape by value, or point at it by URN and keep it an independent artifact. See References between schemas above for worked examples of each, and UML Modelling of Elements for how these constructs map to a (planned) UML authoring UI.

Registry semantics​

Model Forge interprets all JSON Schema references as a directed model graph. Per Element the registry knows:

  • References — outgoing $refs in the content
  • Dependencies / Dependents — forward and reverse edges in the graph
  • Versions — the stored artifact_version history
  • Metadata — title, authored format (jsonschema | xsd), availableFormats (stored + derivable), content type

Domain relationships beyond data structure (ownership, governance, responsibilities) are deliberately not part of the core model. The core is based exclusively on JSON Schema, JSON Schema references, the stored artifact references and the model graph that emerges from them.

Views over the same artifacts​

Different consumers need different projections of the same model inventory. All views are generated on demand and never stored (ADR 055):

ViewPurpose
Persistence viewThe individual stored artifact version (raw stored schema)
Registry viewSemantic view: references, dependencies, dependents, versions
Inlined viewFully resolved single document — runtime, code generation, export
Bundled viewAll transitive dependencies embedded under $defs — portable, self-contained
TypeScript / ZodClient types generated into portal-frontend/src/generated/core/

The inlined and bundled views are described with examples in Model-Centric Data Flow.

DataSets as composition objects​

DataSets contain no embedded data structures — they reference registry objects by URN (ADR 051):

{
"$schema": "https://civitasconnect.digital/core-dataset/v1",
"id": "urn:core:platform:civitas:dataset:common:weather-import:fumcnrtedq:1.0.0",
"datastructureRefs": [
"urn:core:platform:civitas:datastructure:common:weather:6niukwegew:1.0.0",
"urn:core:platform:civitas:datastructure:common:ngsi:a6g3ibjfqg:1.0.0"
],
"mappingRefs": [
"urn:core:platform:civitas:mapping:common:weather-to-ngsi:9l8k3tq8nc:1.0.0"
],
"pipelineRefs": [
"urn:core:platform:civitas:pipeline:common:weather-import:fumcnrtedq:1.0.0"
]
}

DataSets are domain composition objects; the actual models remain reusable registry artifacts. The full manifest format is specified in the CORE-IR Reference.