Skip to main content
Current documentVerified

Create a unit from Ontology and DSL

The model describes classes and fields; a .unit surface defines operations and behaviour. Composer combines them into IR, a plan, C# code and an exposition. The TODO example is a complete input pair, not a customer model. It uses the Composer combination with SDK 1.12.0 and conformance 0.56.0.

Requirements and files​

You need access to a Composer release, its NuGet dependencies, .NET matching that release’s global.json, and the reasoner’s Java and Maven. The reviewed .NET configuration names 10.0.203 with rollForward latestFeature. How to obtain Composer: information to be supplied.

Save the following todo.ttl and todo.unit in a separate directory. createdAt comes from the framework; do not add a competing declaration. In this model listName is a field supplied at create, not host configuration.

@prefix todo: <https://seeraos.dev/ns/ontology-unit/todo#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

# The todo list, as an ontology. One aggregate, four slots, nothing else — the point of the fixture is that a unit
# somebody wrote by hand can be stated as a vocabulary and generated back, so it is deliberately faithful to that
# unit's read model rather than to what a richer todo domain might look like.
#
# `createdAt` is NOT declared here on purpose: the framework stamps it on every aggregate and the read model
# carries it, so a vocabulary that named its own would only collide with the reserved audit member and have its
# own slot renamed.
#
# `listName` is a slot here and is configuration in the hand-written unit: the same value rides every row on the
# wire, stamped from the unit's settings. A generated unit has nowhere to stamp it from, so the caller supplies it
# on create. That difference is real and is the reason this file cannot make the two units byte-identical.

<https://seeraos.dev/ns/ontology-unit/todo> a owl:Ontology ;
rdfs:label "Todo fixture" ;
rdfs:comment "A todo list: a titled item that is done or not, stamped with when it was made." .

todo:id a owl:DatatypeProperty ; rdfs:label "id" .
todo:title a owl:DatatypeProperty ; rdfs:label "title" ;
rdfs:comment "What the item says." .
todo:done a owl:DatatypeProperty ; rdfs:label "done" ;
rdfs:comment "Whether it has been completed." .
todo:listName a owl:DatatypeProperty ; rdfs:label "listName" ;
rdfs:comment "Which list the item belongs to." .

todo:Todo a owl:Class ;
rdfs:label "Todo" ;
rdfs:comment "One item on the list." ;
rdfs:subClassOf
[ a owl:Restriction ; owl:minCardinality 1 ; owl:onProperty todo:id ],
[ a owl:Restriction ; owl:maxCardinality 1 ; owl:onProperty todo:id ],
[ a owl:Restriction ; owl:allValuesFrom xsd:anyURI ; owl:onProperty todo:id ],

[ a owl:Restriction ; owl:minCardinality 1 ; owl:onProperty todo:title ],
[ a owl:Restriction ; owl:maxCardinality 1 ; owl:onProperty todo:title ],
[ a owl:Restriction ; owl:allValuesFrom xsd:string ; owl:onProperty todo:title ],

[ a owl:Restriction ; owl:minCardinality 1 ; owl:onProperty todo:done ],
[ a owl:Restriction ; owl:maxCardinality 1 ; owl:onProperty todo:done ],
[ a owl:Restriction ; owl:allValuesFrom xsd:boolean ; owl:onProperty todo:done ],


[ a owl:Restriction ; owl:minCardinality 1 ; owl:onProperty todo:listName ],
[ a owl:Restriction ; owl:maxCardinality 1 ; owl:onProperty todo:listName ],
[ a owl:Restriction ; owl:allValuesFrom xsd:string ; owl:onProperty todo:listName ] .
unit todo contract todo.v1
expose Todo { crud, lifecycle }
expose graph { query, update }

emits TodoNotification.raised
emits TodoNotification.changed

expose Todo.complete sets done = true announces completed

expose Todo.reopen sets done = false announces reopened

rule todo-created-notice on Todo.created {
emit TodoNotification.raised { todoId = trigger.id, title = trigger.title, done = trigger.done, listName = trigger.listName }
}

rule todo-updated-notice on Todo.updated {
emit TodoNotification.changed { todoId = trigger.id, title = trigger.title, done = trigger.done, listName = trigger.listName }
}

rule todo-completed-notice on Todo.completed {
emit TodoNotification.changed { todoId = trigger.id, title = trigger.title, done = trigger.done, listName = trigger.listName }
}

Model to IR​

COMPOSER_DIR names the selected release sources; CASE_DIR names a fresh input directory. The reasoner resolves the model, checks consistency and writes IR. Imports must match that release configuration.

cd "$COMPOSER_DIR"
mvn -q -f reasoner/pom.xml package
java -jar reasoner/target/reasoner.jar --ontology "$CASE_DIR/todo.ttl" --out "$CASE_DIR/todo.ir.json"

Plan and generation​

Expected outputs are a code directory, exposition.json and a plan. Identity flags must match the surface header. Do not mix historical .eca syntax into it.

dotnet run --project tools/SeeraOS.Units.Ontology.Generator -- --ir "$CASE_DIR/todo.ir.json" --surface "$CASE_DIR/todo.unit" --out "$CASE_DIR/generated-todo" --unit GeneratedTodo --namespace Demo.Todo --segment todo --plan-out "$CASE_DIR/todo.plan.json"

Host and updates​

In a separate Composer copy, use the provided host while preserving its names and dependencies. --unit-dir emits registration materials but does not create a complete host project by itself.

dotnet run --project tools/SeeraOS.Units.Ontology.Generator -- --ir "$CASE_DIR/todo.ir.json" --surface "$CASE_DIR/todo.unit" --out generated/SeeraOS.Units.Generated.Ontology/src --unit GeneratedOntology --namespace SeeraOS.Units.Generated.Ontology --segment ontology
dotnet build generated/SeeraOS.Units.Generated.Ontology

After model or surface changes, regenerate and inspect plan and exposition diffs. Do not edit .g.cs as an independent domain source. Data schema changes need a separate migration plan.

The host needs standard lifecycle, identity, OIDC, Dapr and Control Plane configuration. /.well-known/seeraos-exposition exposes parity; compare the appropriate document in the list with the release artifact. Host RPC uses POST /seeraos-rpc/{entity}/{operation}; Edge routes come from its own projection.

Model inconsistency points to cardinalities and imports. Unknown types or fields point to the model+surface pair. Identity conflicts point to headers and flags. Configuration and auth failures belong to runtime, covered in troubleshooting. Documented generator results do not need rerunning by the documentation author.