Projection#
Turning models into triples and back.
The northbound projection: hiding protocol metadata the ontology declares.
A parameter node bundles two kinds of fact. What the value means — its magnitude, its unit, whether a peer may write it — is northbound content. How the middleware physically reaches the device — a broker address, a topic, an OPC-UA endpoint — is southbound only: a peer holding it could drive the machine directly and bypass every check the middleware performs.
They are not separable by asking for less. PropertySpec._resolve_effective_ranges merges the
whole rdfs:subPropertyOf* chain and there is no merge-depth parameter anywhere in the OGM’s
API, so the shape a consumer gets always contains everything every level declares. The middleware
must therefore cut the bundle itself, and it does so on the shape, before any data is read.
What gets cut is decided by the ontology, not by the code. Walking upward from one parameter property:
level |
contributes |
verdict |
|---|---|---|
the parameter property’s own range |
value, unit |
keep |
protocol markers between it and the root |
broker, topic, … |
delete |
the interface root’s own range |
|
keep |
Deriving the delete set from the registry instead — the union of what the registered binding
descriptors declare — was tried and is wrong: it only knows the protocols this middleware happens
to have code for. Measured, on a belt made reachable over both MQTT and OPC-UA with no OPC-UA
binding registered, the registry-derived set removed the MQTT metadata and served
inf:hasOPCUAEndpoint with its address. Asking the ontology finds it, because the ontology is
authoritative about what a protocol parameter is whether or not anyone wrote a connector.
Recomputed at every startup. Consuming middlewares are decentralized and live in domain experts’ packages; the ontology may have grown a protocol since one of them last looked.
A keep-list — naming what is safe and dropping the rest — was considered and rejected. It reads as the safer construction, but it is a closed-world assertion in the serving path, and the architecture has exactly one closed-world moment by design – SHACL at admission. It would also hide new legitimate domain content by default, taxing twenty domain engineers to guard something the OGM write path already governs.
- exception kapps_semantic_middleware.projection.ProjectionError[source]#
Bases:
RuntimeErrorThe projection could not prove a payload safe, so nothing is served.
Raised rather than logged. A projection that cannot determine what to hide has exactly one safe behaviour, and continuing is not it: the failure would surface as a broker address on a public REST route.
- kapps_semantic_middleware.projection.southbound_properties(ogm: Any, parameter_property: Any, *, interface_root: Any = IRI('https://www.sfb1574.kit.edu/ontologies/CrcInterfaces#isInterfaceAccessibleParameter')) frozenset[source]#
The properties a protocol marker contributes to one parameter’s shape.
Empty for a property that is not interface-accessible at all, which is the ordinary answer for a plain object property like
tu:hasConveyorBelt.
- kapps_semantic_middleware.projection.prune_southbound(class_spec: Any, *, ogm: Any, interface_root: Any = IRI('https://www.sfb1574.kit.edu/ontologies/CrcInterfaces#isInterfaceAccessibleParameter'), cache: Dict[str, frozenset] | None = None) Any[source]#
Return a copy of
class_specwith each parameter’s protocol metadata removed.Recurses, because a resource’s parameters hang off its components. Every nested spec is asked the ontology what its own property contributes southbound, so two parameters reached over different protocols are each pruned correctly.
The input is left untouched: the caller needs both shapes at once — the full one for the bindings to read broker addresses out of, the pruned one to serve.
Copying is deliberately shallow, per spec node, never
copy.deepcopy. A spec’s property keys areIRI, which subclassesrdflib.URIRef, which subclassesstr— deep-copying one reconstructs it throughstr.__reduce_ex__and yields a plainURIRef, which still compares equal but has silently lost thelinedaccessorClassSpec.to_pydantic_modelneeds.cachemaps property IRI to its southbound set. Pass one in to have it filled and reused — every entry is a live SPARQL round trip, and the caller usually needs the same answers again for the binding cross-check.
- kapps_semantic_middleware.projection.carries_southbound(value: Any, southbound: Collection[str]) Set[str][source]#
Which of the given protocol properties appear anywhere in a materialized payload.
The projection’s assertion, usable as a guard or in a test: a northbound payload must contain none of them. A generated model’s field names are IRI-mangled, so this matches the mangled form as well as the raw IRI — the former is what a served JSON body carries, the latter what a spec or a graph dump does.
Mangling goes through
IRI.lined, the OGM’s own field-name derivation, rather than a local copy of the rule: a detector that mirrored it could drift and start reporting “clean” for a payload that leaks.
- kapps_semantic_middleware.projection.cross_check(parameter_property: Any, ontology_declared: Collection[str], descriptor_declared: Collection[Any] | None) None[source]#
Warn when a binding’s declared metadata and the ontology’s disagree.
The ontology decides what is hidden; a descriptor’s
connection_metadatasays what that binding reads. They should coincide, and where they do not, one of the two has drifted:In the ontology only — the protocol contract grew a term this binding ignores. Safe northbound (it is hidden either way), but the connector may be missing configuration.
In the descriptor only — the code expects a term the ontology does not declare. That term will not survive a write and will not reach the connector, so the parameter may come up silently dead. This is the direction that costs debugging time.
Warned, never raised: a drift in either direction is a real deployment state, and the projection is already safe because it follows the ontology.