The seams
The layers are mostly solved: C4, IFML, BPMN, DMN and SHACL each have a vocabulary or a native format that works. What no tool has is the joins between them. SemanticSeams names those joins with a small bridging ontology, seam:, and every cross-layer edge in a model uses one of its predicates. The seams are where the novelty and the value live.
The seam predicates reference describes every predicate, generated from ontology/seam.ttl.
Four design rules
- Few and generic beats many and precise. Thirty-seven predicates. Precision lives in the layer vocabularies and in SHACL shapes over the seams, not in a growing list of specialised edges.
- Every predicate must earn a cross-layer query. If no question needs it, it does not belong in
seam:. - Reuse inside layers, mint only where layers meet. Hydra, SHACL, ODRL and the layer vocabularies do the work inside a layer.
- The navigation backbone stays notation-agnostic, so a new renderer, such as a Gantt chart or a deployment map, plugs in without an ontology change.
The backbone
Three predicates carry the navigation every other seam relies on:
seam:detailedBy: an element to the model that elaborates it. Following it is zooming in.seam:notation: which notation a model is in, and so which renderer the viewer mounts.seam:inModel: an element to the model it belongs to. Membership as data is what lets a SPARQL path cross from a process into its tasks.
The joins
The rest group by the layers they join. A few examples:
| Join | Predicates |
|---|---|
| Architecture to everything | presents (container to its interface), executes (container to the process it runs), schedules (container to the timer-started process whose firings it runs, ADR-0037), exposes and consumes (operations and channels), deployedTo |
| Interface to process | triggers (event to process), completedOn (user task to the form a person completes it on, ADR-0009), visibleWhen (view component to lifecycle state) |
| Process to decisions, communication and data | decidedBy, invokes, publishesTo (a message throw event's completion event, or a service task that streams its invoked operation's deltas onto a channel), subscribesTo (a message start event that subscribes to a channel starts its process on each publish, ADR-0010; on a comm:SemPKMWebhook channel SemPKM is the publisher, ADR-0027), catches (an error boundary event to the operation error it routes, ADR-0013), reads, writes |
| Data to architecture | storedIn (shape to the store container that persists its instances, ADR-0011) |
| Data to lifecycle | lifecycle (class or shape to state machine), aggregateMember (an aggregate, what names a lifecycle, to a shape it commits in one store transaction, ADR-0035), fires (task to transition) |
| Communication contracts | acceptsShape, returnsShape (also a hydra:Status to its problem+json error shape), mediaKind, implementedBy (operation to what serves it: an LLM completion, a Python handler or an HTTP endpoint, ADR-0012) |
| Authorization | governedBy, deliberately attachable to anything |
reads and writes are the workhorses of impact analysis: a property path through triggers, decidedBy and invokes ending in writes answers "what can touch this data".
storedIn is the only seam that ends at a store. With deployedTo it answers "which data is lost if this node goes away". The store's kind (deploy:storeKind), the binding that reaches it (deploy:connectsTo) and, for a SemPKM store, the binding that carries its token (deploy:authenticatesTo, ADR-0025) are deployment facts, so they live in deploy:, not among the seams.
catches is how a failure path crosses layers. An operation declares each error it can raise as a hydra:Status with a comm:errorCode, and the status's returnsShape is the problem+json body. A BPMN error boundary event on a task that invokes the operation is lifted as a proc:ErrorBoundaryEvent and catches one of those statuses. The task it is attached to and the code it matches stay in the BPMN XML, and the runtime checks the two agree when it loads. In the expense tutorial's chapter 9, a notification that cannot be sent takes the expense to finance.
schedules is how time crosses layers (ADR-0037). A BPMN timer start event is lifted as a proc:TimerStartEvent, and the container that runs its firings schedules the process. That container also executes the process, and each timer-started process has exactly one. The fact is listed, not derived from executes, because a process may run in several containers and the one that keeps time is a decision. With deployedTo it answers "which schedules stop if this node goes away". A timer inside an instance, an intermediate catch event or a boundary on a user task, is lifted as a proc:TimerCatchEvent and needs no seam: every container that executes the process can wake it. The expression (timeDate, timeDuration or timeCycle) stays in the BPMN XML, as an error boundary's code does.
How each seam is revealed
Every seam predicate, apart from the administrative inModel, notation and renderPayload, declares a seam:reveal mode in the ontology. The mode tells the viewer what to do with the edge's target:
- Open replaces the view with the target's model:
detailedBy,triggers,completedOn,decidedByandlifecycle. - Nest draws the target inside its source:
presents,executesandschedules. This is what the C4 view's L4 level shows. - Layer overlays the target on the current view. This applies to every other seam. In the per-element view these edges are inspector rows; layer mode draws them as lines between stacked layer planes.
Open and nest seams navigate on click. A model graph can override a predicate's mode with its own seam:reveal triple. The mode is an annotation on the predicate, not a new seam edge, so the predicate count does not change. See ADR-0005 and the viewer guide.
The method applied to itself
Constraints on the seams are not written as rdfs:domain and rdfs:range, because RDFS infers a type onto a violator instead of rejecting it. They are SHACL shapes over the model graph: seam:triggers must start at an ifml:Event, seam:detailedBy must end at a seam:Model, a BPMN model must carry its payload. CI validates every example against them and proves they reject broken seams, and the viewer shows the result on every page.
All seam edges also live in their own named graph, separate from the layer graphs they join (ADR-0001), so the seams can be audited on their own.