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:
| Field | Value |
|---|---|
| artifact type | element (the authored format jsonschema/xsd is a per-version representation, not part of the type) |
| logical URN | urn:core:platform:civitas:element:common:WeatherObservation:6niukwegew |
| versions | 1.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:
| Cardinality | Meaning | JSON Schema mapping |
|---|---|---|
1..1 | required single value | listed in required, scalar schema |
0..1 | optional single value | not in required, scalar schema |
1..* | required, one or more | in required, type: array with minItems: 1 |
0..* | optional, zero or more | not in required, type: array |
m..n | bounded | type: array with minItems: m, maxItems: n |
And the constructs themselves:
| Construct | JSON Schema / CORE-IR mapping |
|---|---|
| Class / entity | type: object document; $id = versioned CORE URN; class name = title |
| Attribute | entry under properties; type → JSON Schema type + validation keywords |
| Primitive type | string (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 / extension | allOf: [ { "$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_versionhistory - 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):
| View | Purpose |
|---|---|
| Persistence view | The individual stored artifact version (raw stored schema) |
| Registry view | Semantic view: references, dependencies, dependents, versions |
| Inlined view | Fully resolved single document — runtime, code generation, export |
| Bundled view | All transitive dependencies embedded under $defs — portable, self-contained |
| TypeScript / Zod | Client 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.