Skip to content

ADR 0007 — What a notation's own keyword becomes, and what it is allowed to type

Status: Accepted, implemented. Its sequence-participant rule is challenged by ADR 0009 (proposed), which argues the fix below was applied to the wrong resource: there are two things per participant — the lifeline and the thing it represents — and the keyword describes the second. If 0009 is accepted, the diagram-kind override in Decision is reverted and the keyword types the structural element again.

Scope: the label a source notation applies on top of its element types — PlantUML's keyword, Structurizr's tags — and the element class that keyword is allowed to resolve to. Retires uml:stereotype.

Depends on: ADR 0006, which retired uml:diagramType for the same reason and by the same test: an undeclared predicate in a published namespace, carrying a Kotlin enum name as a string, when a published vocabulary already existed. This ADR is that argument applied to the element level, and it inherits 0006's diagram-kind detection — the typing fix here depends on knowing which kind of diagram an element is in.

Context

PlantUML output carried a second undeclared predicate:

<…/element/Web_Shop>  a arch:Element, arch:ModelConcept, uml:Lifeline ; uml:stereotype "participant" .
<…/element/Customer>  a arch:Element, arch:ModelConcept, uml:Actor    ; uml:stereotype "actor" .
<…/element/Order_DB>  a arch:Element, arch:ModelConcept, uml:Node     ; uml:stereotype "database" .

Those three are participants on one lifeline axis of one sequence diagram. Reading that output raises two separate questions, and both have unhappy answers.

The predicate is undeclared, and its name claims something false

grep -rn stereotype --include="*.ttl" across linked-archi-meta finds no such property. So uml:stereotype is converter-invented, in the published UML namespace, unconstrainable by any shape — identically to uml:diagramType before 0006.

Worse than 0006's case, though, because UML does publish this mechanism: uml:Profile (474), uml:Stereotype (483) and uml:ProfileApplication (494) are all in uml-onto.ttl. A reader seeing uml:stereotype "participant" will reasonably conclude a UML profile is being applied. Nothing of the kind is happening. The values are PlantUML's own LeafType, GroupType and ParticipantType constants — the keyword the author typed.

And the converter's own field was misnamed the same way, which is how the confusion survived: a user-written <<stereotype>>, which would be a real UML stereotype, is not read by the parser at all.

The keyword was resolved through a class-diagram table, whatever diagram it was in

UmlTypeDefaults.ELEMENT_TYPES maps keyword → class, and it was built for class diagrams. Applied to a sequence diagram it produced the three unrelated metaclasses above. UML 2.5.1 §17.3 is not ambiguous: every participant in an Interaction is a Lifeline. uml:Node is a deployment metaclass and has no business describing a message participant.

Nor were these two granularities of one idea that a consumer could reconcile. uml:Lifeline is rdfs:subClassOf uml:NamedElement; uml:Actor is a Classifier. Different branches of the hierarchy, satisfying different shapes.

The same root cause, a third time

Fixing the above exposed that ELEMENT_TYPES was largely unreachable for entity diagrams, for the reason 0006 documented: PlantUML has three entity-diagram classes and distinguishes far more than three things by USymbol rather than by LeafType.

actor, component, node, artifact and database all arrive as LeafType.DESCRIPTION. Mapping the leaf type alone made every one of them component, so:

Source Was Should be
actor User (use case diagram) uml:Component uml:Actor
node Server uml:Component uml:Node
artifact App uml:Component uml:Artifact
database Store uml:Component uml:Node

The consequence worth stating plainly: uml:Actor was never emitted from any diagram. The "actor" entry in the mapping table was reachable only from a sequence participant — and those are lifelines.

And the concept is uneven across notations

Notation Its version of this Status before
PlantUML keyword emitted, as an undeclared string
Structurizr / C4 tags parsed and discarded — read into C4Element.tags, never emitted
ArchiMate specialization / profile not parsed
BPMN — no analogue

Structurizr's is the same gap C4ViewType had before 0006: read off disk, then thrown away. Views can filter on tags, so this was the data a FILTERED view is defined by.

Options

A. Declare uml:stereotype upstream and enumerate its values

Closes the "undeclared" objection and nothing else. The value set is PlantUML's rendering vocabulary, so it fails DD-7's test for the closed-enumeration pattern — there is no source specification fixing it, and PlantUML is not an authority we can cite in a uml: term. It also leaves the name asserting a profile application that is not happening.

B. Model it as a real UML stereotype

Use uml:Stereotype and uml:ProfileApplication properly — mint a stereotype individual per keyword, apply it. Semantically the closest thing UML has to what the name claimed.

Rejected because the premise is false. A PlantUML keyword is not a stereotype; it is the keyword. database Order_DB asks for a cylinder to be drawn. Manufacturing a UML profile to hold a rendering choice would put a fabricated M2 structure in every graph, and it would still not describe the real <<stereotype>> syntax, which the parser does not read.

C. Relocate it to a converter-owned namespace

puml:keyword in something like https://meta.linked.archi/plantuml/onto#, as retained source data. Removes the false claim about UML and loses nothing.

Rejected for one concrete reason: https://meta.linked.archi/plantuml/onto returns 404, and there is no PlantUML asset published — deliberately, because PlantUML is a syntax whose output is typed in UML, which is the position PublishedAssets already records. So this trades an undeclared term in a namespace that exists for an undeclared term in a namespace that does not.

D. Split it by job, and publish only what survives

Three jobs are being conflated. Give each the home it already has:

Job Values Home
Restates the element's class class, interface, package, usecase, state, enum, participant, … nothing — drop
A real UML attribute in disguise abstract uml:isAbstract, published at uml-onto.ttl:1932
A distinction the class cannot carry boundary, control, entity, database, queue, … schema:keywords

Decision

D, with schema:keywords as the shared home across notations.

<…/element/Ui>       a …, uml:Class ;    schema:keywords "boundary" .
<…/element/Shape>    a …, uml:Class ;    uml:isAbstract true .
<…/element/Order>    a …, uml:Class .                                  # `class` says nothing more
<…/element/Order_DB> a …, uml:Lifeline ; schema:keywords "database" .   # in a sequence diagram

uml:stereotype is removed rather than kept alongside, for 0006's reason: nothing consumed it, and two predicates for one fact drift.

Why schema:keywords. There is no arch: property for "a notation's own classification label" and no C4 tag term either, so every Linked.Archi option meant inventing a third undeclared predicate. schema:keywords is published, dereferenceable, means exactly "keywords or tags describing this item", and schema: is already in use here for schema:name, schema:image and the folder list. It is honestly a label, which is what these are — it makes no type claim, so it cannot be mistaken for one.

Which keyword survives, as a derived rule rather than a table. Publish the keyword only when it does not name the class the element was typed with. class → uml:Class drops out; boundary → uml:Class survives, because the keyword is the only record that the author drew a robustness stereotype.

Derived rather than tabulated because the resolved class is context-dependent: actor resolves to uml:Actor in a use case diagram and to uml:Lifeline in a sequence diagram, so the same keyword is redundant in one and informative in the other. A table would have to encode the context twice and could disagree with itself. A --type-mapping override is followed for free — map database to something named Database and the keyword drops out on its own.

Two documented exception sets, because a string comparison cannot see two kinds of redundancy:

  • Another predicate states it — abstract (uml:isAbstract), initialState / finalState (uml:pseudostateKind already says which kind).
  • It restates its class in different letters — enum abbreviates uml:Enumeration; participant is PlantUML's default participant, the absence of a shape choice, so uml:Lifeline says everything it says. Its siblings actor and database are genuine choices and do survive.

Every sequence participant is uml:Lifeline. The diagram kind now takes part in type resolution. Only sequence diagrams are overridden — outside an Interaction actor genuinely is a uml:Actor — which is why this reads the diagram kind rather than changing the mapping table.

What a lifeline stands for is uml:Lifeline::represents in UML, published as uml:represents. It is not emitted: its range is a ConnectableElement this converter does not synthesise, and inventing one to hold a shape choice would be option B's mistake in miniature. The actor-ness survives as a keyword, which is the honest weight to give it.

The USymbol is read for the keyword, not just for the diagram kind. One ordered table, DESCRIPTION_SYMBOL_KEYWORDS, now serves both — the element's keyword and 0006's deployment-versus-component decision. Those were two substring lists that could disagree.

Structurizr tags are emitted, minus the ones Structurizr wrote itself. A workspace says tags: "Element,Container,Database" — two automatic tags and the author's one. Element is true of every element in the workspace and Container restates c4:Container, so the structural set is dropped and Database is published. Fixed set rather than a derived comparison, because Structurizr spells its tags with a space (Software System) while the class is c4:SoftwareSystem.

ArchiMate specialization is deliberately not mapped here. It is a metamodel-level specialization that can carry its own attributes — much closer to a real UML stereotype than either of the above — and collapsing it onto a label predicate would make "what kind of thing is this" untrustworthy in a merged graph. The converter does not read it yet; when it does, it wants its own answer.

Consequences

  • uml:stereotype no longer appears in any converter output. Verified across the playground corpus.
  • PlantUML element types change. A use case actor moves from uml:Component to uml:Actor, node and artifact from uml:Component to uml:Node / uml:Artifact, and every sequence participant to uml:Lifeline. Element IRIs are unchanged — identity is name-derived, so nothing is re-addressed; only rdf:type moves.
  • uml:Actor, uml:Device and uml:Artifact are reachable for the first time.
  • "Which classes are abstract" is a one-hop query instead of requiring knowledge of PlantUML's keyword vocabulary.
  • Structurizr tags reach the graph. schema:keywords is multi-valued, and relationships carry it too.
  • The detection has consequences it did not have before, and reads PlantUML's internal USymbol class names — the same coupling that caused all three defects. Every keyword and every resulting class is now pinned by a test, so a PlantUML rename fails the build rather than quietly relabelling every actor as a component.

Interaction with name-derived identity

Element ids are slugified entity codes, scoped to the model (ADR 0002, ADR 0010). So a Customer drawn in a class diagram and a Customer participant in a sequence diagram of the same model are one IRI, and that IRI now carries both uml:Class and uml:Lifeline.

That is pre-existing and not made worse here — before this change it carried uml:Class and uml:Actor, which is the same shape of problem. Worth stating because the lifeline rule makes it easier to reach: a participant name that matches a class name merges. Recorded as an open question rather than fixed, because the fix is about identity scope, not about typing.

Example

@startuml
' use case diagram
actor User
User --> (Checkout)
node Server
artifact App
database Store
@enduml
<…/element/User>     a …, uml:Actor .
<…/element/Checkout> a …, uml:UseCase .
<…/element/Server>   a …, uml:Node .
<…/element/App>      a …, uml:Artifact .
<…/element/Store>    a …, uml:Node ; schema:keywords "database" .

Only database publishes a keyword: the other four name their own class, while uml:Node does not say "database".

@startuml
' sequence diagram — the same keywords, a different resolution
actor Customer
participant Web_Shop
database Order_DB
Customer -> Web_Shop: order
Web_Shop -> Order_DB: save
@enduml
<…/element/Customer>  a …, uml:Lifeline ; schema:keywords "actor" .
<…/element/Web_Shop>  a …, uml:Lifeline .
<…/element/Order_DB>  a …, uml:Lifeline ; schema:keywords "database" .

Cross-notation, which is the point of choosing one predicate:

PREFIX schema: <https://schema.org/>

# every element any notation tagged as a database, whatever it was typed as
SELECT ?e ?tag WHERE {
  ?e schema:keywords ?tag .
  FILTER(LCASE(?tag) = "database")
}

Open questions

  • arch:isAbstract, arch:isAbstractClass and uml:isAbstract are three different things, and the first is misnamed. They look like duplicates and are not:
Term Level Means
arch:isAbstract (core-onto.ttl:793) M1, on arch:Element TOGAF Architecture Building Block vs Solution Building Block
arch:isAbstractClass (core-onto.ttl:806) M2, owl:AnnotationProperty on the ontology's own OWL classes not palette-ready, do not instantiate
uml:isAbstract (uml-onto.ttl:1932) M1, on uml:Classifier UML 2.5.1 §9.2 Classifier::isAbstract

So they should not be merged: an ArchiMate ABB is not an abstract class, and an abstract UML class is not necessarily a reusable building block. The problem is the name — arch:isAbstract's own definition never mentions abstract classes, it talks about ABBs and SBBs. Renaming it to something like arch:isBuildingBlock would remove a trap that has already caught a reader. Also uses rdfs:domain where DD-8 wants arch:domainIncludes. Both are upstream naming issues, not filed yet.

  • Real UML <<stereotypes>> are silently dropped. class Order <<entity>> is a genuine stereotype application and the parser reads none of it. That would deserve uml:Stereotype and uml:ProfileApplication — option B applied where its premise is actually true. Not implemented, and it is the one case where PlantUML has real UML profile semantics to offer.
  • Should the participant shape reach the visual layer instead? actor and database on a lifeline are rendering choices, and arch-vis is the layer for those. arch-vis:shapeType exists but its enumeration is eight pure geometries — Rectangle, Ellipse, Hexagon, … — with no cylinder or actor figure, so it would need an upstream extension. schema:keywords is the honest interim.
  • Should there be an arch: term for a notation's classification label? schema:keywords works and invents nothing, but it is a generic web-content property, so a merged graph cannot distinguish "the author tagged this" from "the notation's keyword was retained". If ArchiMate specialization is ever mapped, this needs settling first, because that is a third meaning again.
  • A participant name matching a class name merges the two elements. See the identity note above. ADR 0009 proposes resolving this rather than documenting it, by making the lifeline its own resource linked to the element with uml:represents.
  • "Absent is honest" is wrong for uml:represents specifically. The decision above declines to emit it on the grounds that absence claims nothing. UML disagrees: uml:represents's own skos:scopeNote says an absent value means the self lifeline of the enclosing classifier, so five lifelines with no represents read as five self-lifelines in one Interaction. The same trap applies to uml:sendEvent / uml:receiveEvent, whose absence umlsh:MessageShape reads as found and lost. Corrected in ADR 0009.