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:pseudostateKindalready says which kind). - It restates its class in different letters —
enumabbreviatesuml:Enumeration;participantis PlantUML's default participant, the absence of a shape choice, souml:Lifelinesays everything it says. Its siblingsactoranddatabaseare 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:stereotypeno longer appears in any converter output. Verified across the playground corpus.- PlantUML element types change. A use case
actormoves fromuml:Componenttouml:Actor,nodeandartifactfromuml:Componenttouml:Node/uml:Artifact, and every sequence participant touml:Lifeline. Element IRIs are unchanged — identity is name-derived, so nothing is re-addressed; onlyrdf:typemoves. uml:Actor,uml:Deviceanduml:Artifactare 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:keywordsis multi-valued, and relationships carry it too. - The detection has consequences it did not have before, and reads PlantUML's internal
USymbolclass 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:isAbstractClassanduml:isAbstractare 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 deserveuml:Stereotypeanduml: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?
actoranddatabaseon a lifeline are rendering choices, andarch-visis the layer for those.arch-vis:shapeTypeexists but its enumeration is eight pure geometries —Rectangle,Ellipse,Hexagon, … — with no cylinder or actor figure, so it would need an upstream extension.schema:keywordsis the honest interim. - Should there be an
arch:term for a notation's classification label?schema:keywordsworks 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:representsspecifically. The decision above declines to emit it on the grounds that absence claims nothing. UML disagrees:uml:represents's ownskos:scopeNotesays an absent value means the self lifeline of the enclosing classifier, so five lifelines with norepresentsread as five self-lifelines in one Interaction. The same trap applies touml:sendEvent/uml:receiveEvent, whose absenceumlsh:MessageShapereads as found and lost. Corrected in ADR 0009.