Skip to content

ADR 0009 — A lifeline is not the thing it represents

Status: Proposed — not implemented. Written to be implemented from; the two sub-decisions that blocked implementation are settled below.

Scope: how a PlantUML sequence participant becomes RDF: what is a uml:Lifeline, what is the structural element it stands for, how uml:represents links them, and what a uml:Message connects.

Depends on: ADR 0002 and ADR 0005 for the name-derived, model-scoped identity that makes this necessary at all.

Supersedes part of ADR 0007. 0007 stopped a lifeline being typed uml:Actor or uml:Node, which was right. It did so by making the element a uml:Lifeline, which was the wrong resource to change — see The mistake in 0007.

Context

uml:represents is the association from Lifeline to a ConnectableElement: it answers "what is this lifeline a lifeline of?" A Lifeline is not a classifier; it is the projection of one participant onto the time axis of an Interaction. UML 2.5.1 §17.3 gives it multiplicity [0..1], and the target is typically a Property — a part or role of the BehavioredClassifier that owns the Interaction — though Port and Parameter are also allowed.

This is the highest-value property in the interaction package for our purposes, and for the reason it exists: it is the only edge that lets a query walk from something happening in an interaction to the structure it happened to, and from there onward to an ArchiMate component the class realises.

What the published ontology says, with two wrinkles

:represents  a owl:ObjectProperty ;
    arch:domainIncludes :Lifeline ;
    arch:rangeIncludes  :Property ;      # ← Property only
    skos:definition "References the ConnectableElement (Property, Port, or Parameter) …" ;
    skos:scopeNote  "… If represents is absent, the Lifeline represents the classifier
                     itself (the 'self' lifeline)." .
  • uml:ConnectableElement is not declared. The definition names it and umlsh:LifelineShape's message names it, but no such class exists in uml-onto.ttl. uml:Property, uml:Port, uml:Parameter and uml:TypedElement all do; their common supertype does not. So the axiom narrows to Property while the prose promises three, and neither is enforceable against a class that is absent.
  • The shape constrains cardinality only. umlsh:LifelineShape has sh:maxCount 1 on uml:represents and no sh:class. Whatever we point it at will validate. The choice is therefore entirely a question of what is true, not of what passes.

Why we cannot emit it today

Not because of the range. Because there is no second resource to connect. Element ids are slugified entity codes scoped to the model (ADR 0010), so for a participant referred to as Customer:

  • if a class Customer exists in the same model, it is the same IRI — represents would be a self-loop;
  • if it does not, the participant element is the only resource bearing that name — there is nothing to point at.

In the current playground corpus it is the second case throughout. The first is real by design: a colliding name yields one IRI carrying both uml:Class and uml:Lifeline, which is the conflation 0007's identity note flagged.

The uncomfortable corollary: the cross-model walk this ADR is about already works, and it works because of that conflation. If Customer is one IRI, a message reaches the ArchiMate component in one join and no represents is needed. It is free, and it is wrong — uml:Lifeline is rdfs:subClassOf uml:NamedElement, not a Classifier, so the merged resource asserts two incompatible things about one subject.

Absence is not neutral, in two places

Both are cases where omitting a triple is itself a claim under UML reading rules:

Omission Reads as Our output today
uml:represents absent the self lifeline of the enclosing classifier every lifeline — so five self-lifelines in one Interaction
uml:sendEvent absent a found message every message
uml:receiveEvent absent a lost message every message

umlsh:MessageShape states the second and third explicitly in its own messages. So today every message is simultaneously found and lost, and every lifeline is the self lifeline. This is not a new defect introduced by anything recent, but it does undercut the "absence is honest" argument used in 0007 for not emitting represents — silence here is a quieter falsehood, not a neutral one.

The mistake in 0007

0007 observed that a sequence diagram produced uml:Lifeline, uml:Actor and uml:Node on one lifeline axis, and fixed it by resolving every sequence participant to uml:Lifeline. The observation was right and the fix was applied to the wrong resource.

There are two things per participant, and the keyword describes one of them:

What it is What types it
the lifeline a projection onto the interaction's time axis, one per interaction always uml:Lifeline
the thing represented the structural participant the keyword — actor → uml:Actor, database → uml:Node

Seen that way the original keyword mapping was never wrong; it was being applied to a resource that did not exist yet. 0007 removed a false statement by deleting information rather than by separating the subjects.

Options

A. Status quo: keep the merge, emit no represents

Free join, no work. One IRI is both a Classifier and a Lifeline, represents-absence reads as self-lifeline, and a consumer cannot ask "which lifelines are there" without also getting classes.

B. Split, and synthesise the Property UML asks for

Lifeline --represents--> Property --type--> Class. Strictly correct.

Rejected on this codebase's own stated principle. UmlTypeDefaults.messageKindIri deliberately never emits uml:messageKind uml:Complete because the converter emits no uml:MessageEnd resources and so "would be claiming a fact about a structure it does not model". A synthesised Property is that twice over: UML requires the Property to be a part of the classifier owning the Interaction, and PlantUML gives us no such classifier either. Two fabricated resources per participant, one of them nameless.

C. Split, and point represents at the structural element directly

Lifeline --represents--> Actor | Node | Class | …. Deviates from the Property range, invents nothing, and delivers the walk.

Decision

C, with the structural element typed by the keyword and the lifeline scoped to its view.

## actor Customer / participant Web_Shop / database Order_DB, in view `checkout`

<…/element/Customer>  a arch:Element, arch:ModelConcept, uml:Actor ;
    skos:prefLabel "Customer"@en .

<…/view/checkout/lifeline/Customer>  a arch:Element, arch:ModelConcept, uml:Lifeline ;
    skos:prefLabel   "Customer"@en ;
    uml:represents   <…/element/Customer> ;
    schema:keywords  "actor" .

<…/element/Order_DB>  a arch:Element, arch:ModelConcept, uml:Node ;
    schema:keywords "database" .
<…/view/checkout/lifeline/Order_DB>  a …, uml:Lifeline ;
    uml:represents <…/element/Order_DB> .

Four parts, each of which was a real choice.

1. The lifeline is a new resource, addressed under its view. …/view/{viewId}/lifeline/{participantId}, minted with the existing IriMinting.customIri. View-scoped because a lifeline belongs to one Interaction and the same participant may appear in several — which is precisely what the current model-scoped element cannot express. No existing IRI moves: …/element/{id} keeps its address and its meaning, and the lifeline is additive.

2. The keyword types the structural element again, and the lifeline is always uml:Lifeline. This restores what 0007 removed, at the resource where it was true all along. actor → uml:Actor, database → uml:Node, boundary → uml:Class. participant — PlantUML's default — says nothing structural, so the element gets arch:Element and arch:ModelConcept and no UML metaclass. That is honest rather than untyped-by-accident: it is a named participant whose metaclass the source genuinely does not state, and inventing uml:Class for it would be option B's error in miniature.

3. arch:source / arch:target on a Message stay on the structural elements. Unchanged, so nothing migrates and no query breaks. P-7 requires the direct triple for analytics, and "does ClientApp call OrderService" must not become a three-hop path through lifelines. The UML-faithful chain — Message → sendEvent → MessageOccurrenceSpecification → covered → Lifeline — is deferred, see Phase 2.

4. There is no uml:Interaction resource in phase 1, and the view is not typed as one. Typing the view uml:Interaction was the obvious move and it is wrong: uml:Interaction ⊑ uml:Behavior ⊑ uml:Class ⊑ … ⊑ uml:Element ⊑ arch:Element, and also uml:Interaction ⊑ uml:InteractionFragment ⊑ uml:NamedElement ⊑ uml:Element ⊑ arch:Element. So it would make every sequence view an arch:Element, inflating every element count with things that are not elements. If an Interaction is wanted it needs its own resource, which is phase 2. The per-interaction scoping that matters now is carried by the lifeline's address.

What represents points at, and the range deviation

arch:rangeIncludes uml:Property is guidance, not inference — that is the whole point of DD-8 — and no shape enforces it, so pointing at uml:Actor or uml:Node produces no violation and no wrong entailment. It is nonetheless a deviation, and it is recorded as one rather than glossed: we are saying "this lifeline stands for that participant" where UML says "…for that part of the enclosing classifier". The intermediate Property is the thing PlantUML does not have.

The alternative is to leave represents empty and let it read as self-lifeline, which is a worse statement, not a more cautious one.

Phase 2, deferred deliberately

Not in scope here, listed so it is not rediscovered:

  • uml:Interaction as its own resource, with uml:hasLifeline and uml:hasMessage. Needs an answer to how it relates to the view without the view becoming an arch:Element.
  • MessageOccurrenceSpecification pairs, giving uml:sendEvent / uml:receiveEvent and closing the found/lost trap. umlsh:OccurrenceSpecificationShape requires uml:covered exactly 1 and uml:sequenceNumber exactly 1 — both of which PlantUML can supply, since it gives message order. The cost is two extra nodes per message: on the measured corpus, ~3,100 for 1,567 messages.
  • Real <<stereotypes>> via uml:Stereotype / uml:ProfileApplication, from 0007's open questions.

Consequences

  • The interaction-to-structure walk becomes an explicit one-hop edge instead of an accident of naming, and it works whether or not a class diagram in the same model happens to use the same name.
  • uml:Class and uml:Lifeline stop sharing a subject. 0007's identity-merge note is resolved rather than documented: a Customer class and a Customer participant are now two resources with an edge between them, which is what they are.
  • The same participant in two sequence diagrams of one model becomes two lifelines representing one element — correct, and not currently expressible.
  • Element counts rise. uml:Lifeline is an arch:Element by inheritance (uml:Lifeline ⊑ uml:NamedElement ⊑ uml:Element ⊑ arch:Element), so a five-participant sequence diagram goes from five elements to ten. Defensible under P-9 — a lifeline is a node on a diagram, it renders, it has identity — but it is a visible change to every count and every folder listing, and it is the main cost of this ADR.
  • Existing element IRIs and message IRIs are unchanged, so there is no republish and no identity-lock concern. Only additions, plus rdf:type moving back on the structural element.
  • schema:keywords moves to the lifeline for shape keywords (actor, database) since that is what the shape describes, and stays on the element for structural ones. Worth checking against 0007's derived rule during implementation — the rule compares the keyword against the resolved class, and there are now two classes in play.
  • A view node (archvis:ArchNode) currently points at the element via archvis:archElement. It should point at the lifeline, because that is what is drawn on a sequence diagram. This is the one place an existing triple changes meaning.

Implementation plan

  1. PumlModel: sequence participants keep producing one PumlElement; add nothing to the model. The split is an emitter concern — the parser already has everything.
  2. IriMinting: add lifelineIri(viewId, participantId) rather than using customIri ad hoc, so the scheme is in one place with the others.
  3. UmlTypeDefaults: revert the resolveElementType(keyword, ns, diagramType) overload added by
  4. With the split, the diagram kind no longer changes how the element is typed. Keep a documented default for participant that yields no UML class.
  5. LinkedArchiEmitter (PlantUML), sequence path only: emit the structural element as today minus the 0007 override; emit a lifeline per participant per view, typed uml:Lifeline, labelled, with uml:represents at the element; move the shape keyword to the lifeline.
  6. archvis:archElement on sequence view nodes: retarget to the lifeline.
  7. Tests: extend KeywordAndLifelineTest; the every sequence participant is a lifeline case inverts — the lifeline resource is the Lifeline and the element is an Actor/Node/untyped.
  8. Amend 0007 in place: its sequence-override section is superseded here, and its "absent is honest" line on represents is wrong for the self-lifeline reason.
  9. docs/converters/plantuml.md: the sequence section needs the two-resource picture.

Example

@startuml
' checkout.puml — view `checkout`
actor Customer
participant Web_Shop
database Order_DB
Customer -> Web_Shop: place order
Web_Shop -> Order_DB: persist
@enduml
<…/element/Customer> a …, uml:Actor  ; skos:prefLabel "Customer"@en .
<…/element/Web_Shop> a arch:Element, arch:ModelConcept ; skos:prefLabel "Web Shop"@en .   # no UML class
<…/element/Order_DB> a …, uml:Node   ; schema:keywords "database" .

<…/view/checkout/lifeline/Customer> a …, uml:Lifeline ;
    uml:represents <…/element/Customer> ; schema:keywords "actor" .
<…/view/checkout/lifeline/Web_Shop> a …, uml:Lifeline ;
    uml:represents <…/element/Web_Shop> .

<…/relationship/Customer__Web_Shop__place_order__…> a …, uml:Message ;
    uml:messageSort uml:SynchCall ;
    arch:source <…/element/Customer> ;      # unchanged — the analytics contract
    arch:target <…/element/Web_Shop> .

The query this is for, across notations:

# from a message in an interaction to the ArchiMate component the participant realises
SELECT ?message ?component WHERE {
  ?message a uml:Message ; arch:target ?element .
  ?lifeline uml:represents ?element .
  ?element  am:realizes ?component .
}

Open questions

  • Should represents point at a Property after all, if one is ever derivable? A class diagram in the same model that declares Web_Shop with a part could in principle supply one. Nothing in PlantUML connects a sequence participant to a class attribute, so this stays hypothetical.
  • Does the participant default deserve a UML class? It gets none here. uml:Class would be a guess; uml:Classifier is abstract and arch:isAbstractClass-annotated upstream, so it is not instantiable either. Leaving it at arch:Element is the honest reading, but it does mean a sequence-only model has elements with no metaclass, which the ConversionVerifier may want to stop reporting as incomplete.
  • Should the lifeline live in the semantic graph or the views graph? It is addressed under a view and it is a uml:Lifeline, which is model content, not layout. Semantic graph is the proposal, on the grounds that a lifeline is a UML metaclass instance and archvis: holds geometry — but it is genuinely arguable, and the archvis:archElement retarget in step 5 crosses the boundary either way.
  • uml:ConnectableElement is missing upstream, and uml:represents narrows to Property while its own prose says Property, Port or Parameter. Not filed in todo/UPSTREAM-ISSUES-meta.md; it would make the range guidance match the specification and give the shape a class it could actually check.