Orchestrata Ultimate / Specs / 105-ontology-domain-model

105-ontology-domain-model: Ontology-Driven Domain Model Generation

A skill that reads an ontology (OWL/RDFS in Turtle/.ttl, i.e. RDF triples) and generates a domain model as a screaming-architecture / modular monolith with centralized logging and exception handling. Generation is deterministic (Jinja2 templates → idempotent output) and bidirectional: a canonical intermediate representation (IR) drives both the ontology and the code, so adding a node/class regenerates both. Generated entities and relationships persist to Stardog...

At a glance

Item Value
Spec directory specs/105-ontology-domain-model/
Status line in the spec In Review
Requirement IDs defined 50
With a @traces citation in src/ 28 (56.0%)
With a @traces citation in tests/ 0 (0.0%)
Tracked files 2
RTM page 105-ontology-domain-model

Reading this spec

This spec defines a skill that reads an ontology (OWL or RDFS in Turtle, that is RDF triples) and generates a domain model from it, as a screaming-architecture modular monolith with centralized logging and exception handling. Hand-writing a domain model and its persistence drifts from the ontology and buries audit history, so everything is driven from one canonical intermediate representation (IR). Generation is deterministic (Jinja2 templates, idempotent output) and bidirectional: the IR drives both the ontology and the code, so adding a node or class regenerates both. Generated entities and relationships persist to Stardog with temporal (bitemporal) and insert-only semantics for nodes and edges alike.

Four user stories cover generating a domain model from an ontology, adding a class and regenerating both ways, persisting with full history and no destructive writes, and getting centralized logging and exceptions in generated code. The eight functional requirements (FR-001 to FR-008) are ontology to IR, IR to ontology, round-trip, IR to code, centralized logging and exceptions, Stardog persistence, add-a-node bidirectional regeneration and constraint generation with SHACL.

The 50 requirement IDs (REQ-105-NNN) record the rules. An owl:Class or rdfs:Class becomes an Entity in the IR; rdfs:subClassOf sets a single parent (several parents keep the first with a warning); IRI local names are extracted while the full IRI is preserved; an object property without a range is skipped with a warning instead of crashing. Round-trip preserves namespace, labels, comments and cardinality. Every generated module uses core.logging.get_logger. Every assertion carries a transaction time, a logical delete inserts a tombstone that closes :validTo and never removes a triple, and generated SPARQL is parameterized with IRIs validated before interpolation. SHACL constraints are emitted as constraints.ttl, loadable into Stardog's integrity constraint validation.

The code is in the domaingen package: reader (15 requirements), generator, ttl, constraints and stardog. The requirements traceability matrix lists each ID. The spec's own status line reads In Review and it tracks 2 files. Measured coverage is 28 of 50 requirements cited by at least one @traces in src/ (56.0%), spread over 6 modules; the module citing the most of them is orchestrata_ultimate/domaingen/reader (15 requirements). In tests/, 0 requirements are cited by a test-side @traces. 22 requirements have no citing symbol, most of them in the bitemporal persistence group (REQ-105-027 to 039); the behaviour may still exist in code that was never annotated.

Requirements

Checkbox is the state recorded in the requirements checklist; Traced by is the count of src/ symbols whose @traces names the ID.

ID Done Requirement Traced by
REQ-105-001 yes owl:Class / rdfs:Class → an Entity in the IR. 2
REQ-105-012 yes Accept the ontology as a .ttl file path or raw Turtle text. 2
REQ-105-013 yes Parse via rdflib (Graph().parse, Turtle); RDF-star input tolerated. 2
REQ-105-014 yes owl:DatatypeProperty with rdfs:domain D → a Field on entity D. 1
REQ-105-015 yes owl:ObjectProperty with rdfs:domain D, rdfs:range R → a Relationship on D targeting R. 2
REQ-105-016 yes rdfs:subClassOf → Entity.parent (single; multiple parents → first + warning). 2
REQ-105-002 yes Cardinality — owl:FunctionalProperty / owl:maxCardinality 1 → many=False; otherwise many=True. none
REQ-105-017 yes owl:minCardinality >= 1 (or owl:someValuesFrom) → Field/Relationship.required=True. 3
REQ-105-018 yes xsd datatype → Python type map (string→str, integer/int→int, decimal/double/float→float, boolean→bool, dateTime/date→datetime); unknown → str + warning (ERR-105-003). 1
REQ-105-019 yes rdfs:label → human name (fallback: IRI local name); rdfs:comment → docstring. 1
REQ-105-020 yes IRI local-name extraction (after # or final /); preserve full iri on the IR node. 1
REQ-105-021 yes owl:inverseOf → Relationship.inverse. 1
REQ-105-022 yes Entities group into Modules (bounded contexts); default = one module named after the ontology. none
REQ-105-023 yes The ontology base IRI / prefix is captured on DomainModel.namespace. 2
REQ-105-024 yes Parse failure raises OntologyParseError(BaseError) with file + cause (ERR-105-001). 2
REQ-105-025 yes Object property with no rdfs:range is skipped with a warning (ERR-105-002), not a crash. 2
REQ-105-003 yes Render a DomainModel to valid Turtle (classes, datatype/object properties with rdfs:domain/rdfs:range, rdfs:subClassOf). 5
REQ-105-004 yes parse_ontology(render_ontology(ir)) equals ir structurally (entities, fields, relationships, inheritance). 2
REQ-105-026 yes TTL rendering is deterministic — sorted entities/properties, stable prefixes, no timestamps. 4
REQ-105-011 yes Adding an Entity and/or Relationship to the IR regenerates both the ontology TTL and the code consistently. 1
REQ-105-027 yes Round-trip preserves namespace, labels, comments, and cardinality. none
REQ-105-008 yes The generated repository emits only INSERT SPARQL; no UPDATE/DELETE is ever produced. 1
REQ-105-028 yes Each node attribute is an RDF-star assertion << :e :attr v >> annotated with provenance/temporal metadata. none
REQ-105-029 yes Relationship (edge) triples << :a :rel :b >> are annotated identically — temporality applies to edges as well as nodes. none
REQ-105-030 yes Annotations are append-only; no existing triple/annotation is mutated in place. none
REQ-105-031 yes Every assertion carries prov:txFrom (transaction time, when recorded, UTC xsd:dateTime). none
REQ-105-032 yes Every assertion carries :validFrom and :validTo (valid time, when true in the domain). none
REQ-105-033 yes Open intervals use a sentinel :validTo "9999-12-31T00:00:00Z" (or unbound), never NULL deletes. none
REQ-105-009 yes Changing a value/edge inserts a new annotated assertion and a tombstone that closes the prior :validTo — both are inserts. none
REQ-105-034 yes A tombstone is itself an inserted assertion; the original assertion remains untouched (full history retained). none
REQ-105-035 yes "Logical delete" = insert a tombstone closing :validTo; the triple is never physically removed. none
REQ-105-010 yes as_of(T) returns node state at instant T via SPARQL-star: txFrom <= T ∧ validFrom <= T < validTo. none
REQ-105-036 yes as_of(T) works for relationships (edge history), not just node attributes. none
REQ-105-037 yes "Current state" view = latest txFrom, not tombstoned, validTo in the future. none
REQ-105-038 yes A separate transaction-time-only ("as-recorded") and valid-time-only query mode is supported. none
REQ-105-039 yes Generated SPARQL is parameterized/escaped; ontology IRIs are validated before interpolation (SEC-105-002). none
REQ-105-040 yes No DELETE/DROP/CLEAR is ever generated; a generation-time check fails the build if present (SEC-105-003). 2
REQ-105-041 yes A named-graph-per-version persistence mode is documented as an alternative to RDF-star (for stores without RDF-star). none
REQ-105-042 yes Stardog connection failure raises RepositoryError/StardogConnectionError (both BaseError); code generation itself is offline and unaffected (ERR-105-005). 5
REQ-105-049 yes generate_shacl emits a sh:NodeShape per entity (sh:targetClass); fields → property shapes with sh:datatype + sh:maxCount 1 (+ sh:minCount 1 when required); relationships → sh:class <target> (+ sh:maxCount 1 single, sh:minCount 1 required); a subclass references its parent via sh:node. 2
REQ-105-050 yes SHACL is deterministic, valid Turtle, emitted as constraints.ttl by generate_code; loadable into Stardog ICV (on-demand icv report, or guard mode to enforce on write). 1
REQ-105-005 yes Per module, generate domain/&lt;module>/{entities.py, repository.py, __init__.py} (relationships are entity attributes + the repository PREDICATES map). 1
REQ-105-043 yes Layout is screaming — top-level packages are named for the domain/bounded context, not the framework. 1
REQ-105-044 yes Modular monolith — a module imports only its own package + shared core; cross-module references go through relationship objects (no direct cross-module imports). none
REQ-105-045 yes Entity class is a dataclass with typed fields; parent reflected via inheritance; required fields have no default, optional default None. none
REQ-105-007 yes Every generated module uses core.logging.get_logger(__name__). 1
REQ-105-046 yes Domain errors inherit core.base.error.BaseError; class names end in Error. none
REQ-105-047 yes Generated code has no print and no bare except:. none
REQ-105-006 yes Codegen is deterministic/idempotent — re-running on the same IR yields byte-identical files (stable ordering, no timestamps). 4
REQ-105-048 yes All TTL/code writes are confined to the target root; path traversal is rejected (SEC-105-001). 2

Code that traces this spec

Module Symbols Requirements
orchestrata_ultimate/core/logging 1 1
orchestrata_ultimate/domaingen/constraints 2 2
orchestrata_ultimate/domaingen/generator 6 6
orchestrata_ultimate/domaingen/reader 10 15
orchestrata_ultimate/domaingen/stardog 5 1
orchestrata_ultimate/domaingen/ttl 5 4

Checklists

checklists/requirements.md

The requirement list above comes from this file; 50 of 50 checkbox items are ticked.

Full spec text

Verbatim from specs/105-ontology-domain-model/spec.md; headings are pushed down two levels.

SPEC-105: Ontology-Driven Domain Model Generation

Metadata


Overview

What

A skill that reads an ontology (OWL/RDFS in Turtle/.ttl, i.e. RDF triples) and generates a domain model as a screaming-architecture / modular monolith with centralized logging and exception handling. Generation is deterministic (Jinja2 templates → idempotent output) and bidirectional: a canonical intermediate representation (IR) drives both the ontology and the code, so adding a node/class regenerates both. Generated entities and relationships persist to Stardog with temporal (bitemporal) and insert-only semantics for both nodes and edges.

Why

Ontologies capture a domain's classes, attributes, and relationships precisely. Hand-writing the matching domain model + persistence drifts from the ontology and buries audit history. Driving everything from one canonical IR keeps the ontology, the code, and the graph in lock-step; insert-only + bitemporal storage gives a complete, queryable history ("as-of") with no destructive updates.

How (pipeline)
   .ttl ontology  ──(rdflib)──►  DomainModel IR  ──(Jinja2, deterministic)──►  code
        ▲                          (canonical)                                  │
        └──────────(render TTL)────────┴───────────(model registry)────────────┘
                                        │
                                        ▼
                         Stardog (RDF-star, insert-only, bitemporal)

The IR is the single source of truth. ontology→IR and IR→ontology round-trip; IR→code is deterministic. Adding an entity to the IR regenerates the ontology and the code together (FR-007).

Architecture of the generated model
Temporal, insert-only RDF model (Stardog)

Nodes (entity attribute assertions) and edges (relationship triples) are never updated or deleted. Each is recorded as an RDF-star–annotated assertion carrying bitemporal stamps:

<< :order_42 :total 19.99 >>  prov:txFrom  "2026-05-28T10:00:00Z" ;
                              :validFrom   "2026-05-28T00:00:00Z" ;
                              :validTo     "9999-12-31T00:00:00Z" .
<< :order_42 :placedBy :cust_7 >>  prov:txFrom "2026-05-28T10:00:00Z" ; ... .

User Scenarios

US-001: Generate a domain model from an ontology

As a developer I want to point the skill at a .ttl ontology and get a screaming-architecture domain model so that code matches the ontology exactly. Acceptance Criteria: OWL classes → entity classes; datatype properties → typed fields; object properties → relationships; rdfs:subClassOf → inheritance; output is deterministic (re-running yields identical files).

US-002: Add a class/node and regenerate both ways

As a developer I want adding an entity to the IR to update both the ontology and the code so that they never drift. Acceptance Criteria: adding an entity + a relationship to the IR regenerates the .ttl (new class + property) and the code (new module/entity/repository); ontology→IR→ontology round-trips structurally.

US-003: Persist with full history, no destructive writes

As an operator I want all node and edge changes stored insert-only with time stamps so that I can query any past state and nothing is ever lost. Acceptance Criteria: the generated repository emits only INSERTs (RDF-star annotated); a value change inserts a new assertion + a tombstone; an "as-of" query returns the state at a past instant; no UPDATE/DELETE SPARQL is generated.

US-004: Centralized logging & exceptions in generated code

As a maintainer I want generated modules to use the shared logging and exception framework so that observability and error handling are uniform. Acceptance Criteria: every generated module imports core.logging.get_logger; domain errors inherit the SPEC-100 base; generated code compiles and lints clean.


Data Models (IR)

Type Fields
DomainModel name: str, namespace: str (ontology base IRI), modules: list[Module]
Module name: str (bounded context), entities: list[Entity]
Entity name: str, iri: str, parent: str | None, fields: list[Field], relationships: list[Relationship], doc: str
Field name: str, type: str (python type from xsd), iri: str, required: bool, doc: str
Relationship name: str, iri: str, target: str (target entity), many: bool, inverse: str | None, doc: str

xsd→python map: xsd:string→str, xsd:int/integer→int, xsd:decimal/double/float→float, xsd:boolean→bool, xsd:dateTime/date→datetime, default→str.


Functional Requirements

FR-001 — Ontology → IR
FR-002 — IR → Ontology
FR-003 — Round-trip
FR-004 — IR → Code (deterministic, screaming architecture)
FR-005 — Centralized logging & exceptions
FR-006 — Stardog persistence: temporal, insert-only (nodes + edges)
FR-007 — Add-a-node bidirectional regeneration
FR-008 — Constraint generation (Stardog ICV / SHACL)

Stardog Integrity Constraint Validation is expressed in SHACL; constraints load into a database and run on demand (icv report) or enforce on write (guard mode).


Error Conditions

ID Trigger Handling
ERR-105-001 Ontology fails to parse (bad Turtle) raise OntologyParseError(BaseError) with file + cause
ERR-105-002 Object property with no rdfs:range skip relationship + warn (cannot type the target)
ERR-105-003 Unknown xsd datatype map to str + warn
ERR-105-004 Output path escapes the target root reject (sandbox, shared with SPEC-103)
ERR-105-005 Stardog connection unavailable StardogConnection raises StardogConnectionError, wrapped by the repository as RepositoryError (both inherit core.base.error.BaseError); generation is offline and unaffected

API Contracts

def parse_ontology(ttl_path_or_text: str | Path) -> DomainModel: ...   # FR-001
def render_ontology(model: DomainModel) -> str: ...                    # FR-002 (TTL)
def generate_code(model: DomainModel, out_dir: str | Path) -> list[Path]: ...  # FR-004
class StardogTemporalRepository:   # generated per entity; insert-only, bitemporal
    def insert(self, entity) -> None: ...
    def supersede(self, iri, **changes) -> None: ...   # insert new + tombstone
    def as_of(self, iri, instant) -> dict: ...         # SPARQL-star point-in-time

Security Requirements


Dependencies

Component Spec/Pkg Purpose
Logging & Exceptions SPEC-100 Generated code's logging + error hierarchy
Jinja2 jinja2>=3.1 (present) Deterministic templates
rdflib rdflib>=7 (added) Parse/serialize OWL/Turtle, RDF-star
Stardog external (SPARQL 1.1 + RDF-star/SPARQL-star) Temporal insert-only persistence
Refactor tools SPEC-103 (optional) the refactor agent may invoke generation as a tool

Testing Requirements


Definition of Done


Revision History

Version Date Author Changes
0.1.0 2026-05-28 Mohamed Initial spec — ontology-driven, bidirectional, temporal insert-only domain-model generation