Skip to content

ADR 0001 — Ownership of model identity

Status: Accepted, implemented

Implementation summary:

  • --require-id refuses to derive a model ID from the input filename. Available on every convert command and on render.
  • PlantUML sources declare their identity in header comments, '!la-model: and '!la-view:, read by both convert and render.
  • A declaration in the source and one in the index must agree; a disagreement fails the run naming both values (IdentityAgreement).
  • Decision 2, cross-repository uniqueness, belongs to the aggregation repository and is not implemented here.

Scope: where model IDs and model-level metadata are declared and who governs changes to them.

Out of scope:

  • Element, relationship and view IDs. These are read from the source file; see Identifiers.
  • Which resource an index entry names, model or view. See ADR 0002. The conclusions here apply to whichever level carries the declared identity.
  • Changing an identity that has already been published, including aliasing a superseded IRI. See ADR 0003.

Context

A model ID is an IRI path segment: {base}{notation}/{modelId}/element/{id}. Declaring one allocates a namespace. Changing one relocates every IRI in that model and renames its rendered SVG.

The ID is currently resolved outside the source file: the diagram index entry, then --model-id, then the input filename. Only the index path is validated.

Two properties of the pipeline constrain the options.

The topology is federated. Each source repository holds a diagram-index.yaml per notation directory and converts locally; the aggregation repository pulls the resulting artifacts and merges them (Authoring Models, Aggregating into a Graph). A "central" index is therefore central per repository and per notation. No global registry exists, and no component verifies that two repositories have not declared the same model ID.

In-file identity is already the rule one level down. Element IRIs derive from in-file identifiers in every converter: BPMN id attributes, ArchiMate Exchange identifier, Structurizr JSON id, Backstage metadata.name, PlantUML entity codes. Model identity is the exception. The reason is scope: an element ID must be unique only within the model IRI enclosing it, whereas a model ID has no enclosing namespace beyond {base}{notation}/.

Decision drivers

Four concerns are currently carried by one file and have different owners and change rates:

Concern Owner Change rate Effect of a change
Identity (id) author declares, platform governs rare relocates every IRI in the model
Selection (status) publisher per release model enters or leaves the graph
Descriptive metadata (title, author, description) author unrestricted provenance triples only
Ownership / stewardship organisational structure on reorganisation governance queries

Selection is a decision about whether to publish an artifact and cannot be expressed inside the artifact being published. Identity is a property of the thing identified. A single location for all four concerns cannot satisfy both.

Options considered

A. Index only (current behaviour)

Identity is declared in the index; the source file contains no identifier.

# models/diagram-index.yaml
model:
  id: order-domain
views:
  - id: order-fulfillment
    file: order-fulfillment.bpmn
    status: publish
Axis Assessment
Conflict detection Duplicate IDs within one repository fail at parse time. Concurrent additions produce a textual merge conflict in one file. Cross-repository duplicates are not detected
Versioning Two coexisting versions are two entries with two IDs. An ID is stable across edits, so re-conversion produces a minimal diff
Authoring Fail-closed: copying a source file creates no identity until an entry is added, so neither collision nor publication can happen by accident. The index is a reviewable manifest of the published set
Cost Two files to keep in step. An omitted entry falls back to an unvalidated filename-derived ID. Authors own the diagram but not its address

B. Source file only

Identity is declared by the diagram. Neither slot below is implemented; the PlantUML form is one candidate among those listed under Open questions.

' order-fulfillment.puml
'!la-model: order-domain
'!la-view: order-fulfillment
@startuml
class OrderService
@enduml
<!-- order-fulfillment.bpmn -->
<definitions id="Definitions_1" targetNamespace="https://example.org/bpmn/">
  <extensionElements>
    <la:model xmlns:la="https://meta.linked.archi/index#" id="order-domain"/>
    <la:view  xmlns:la="https://meta.linked.archi/index#" id="order-fulfillment"/>
  </extensionElements>
  <process id="Fulfilment"/>
</definitions>
Axis Assessment
Conflict detection No shared file, so no merge conflicts and no enforced review of identity changes. Duplicates within a repository remain detectable at conversion time. Copying a source file duplicates its identity. Cross-repository duplicates are not detected
Versioning Two coexisting versions require editing the ID inside the copied file. Omitting that edit causes one version to shadow the other when the copies are in different repositories
Authoring Depends on the notation. PlantUML sources are hand-edited text, so a header line is workable. BPMN sources are tool-generated XML whose only extension point is <extensionElements>, which most modellers do not expose and a re-export may discard
Benefit Identity is carried by the artifact: moving the file, including between repositories, leaves the IRI unchanged. Version control attributes the identity change to the commit that made it

Applied uniformly, B requires BPMN authors to edit exported XML by hand, or a per-notation rule that reintroduces divergence between the BPMN and PlantUML commands.

C. Hybrid, divided along the concern boundary

  • The source file declares identity for notations that can carry it durably.
  • The index owns selection (status), and may pin identity for notations that cannot.
  • Disagreement between the two is an error naming both values. Silent precedence is not permitted.
  • Descriptive metadata keeps the current rule: the source file supplies it, the index overrides it.

The index entry declares only which files are published, and where the ID is pinned rather than read from the source:

# models/diagram-index.yaml
model:
  id: order-domain                 # pinned: BPMN sources cannot carry it durably
views:
  - id: order-fulfillment
    file: order-fulfillment.bpmn
    status: publish
  - file: stock-replenishment.puml # id read from the source file
    status: publish
  - file: draft-checkout.puml
    status: draft

A source file and an index that disagree fail the run rather than one silently winning:

[ERROR] Conversion failed: conflicting view id for 'stock-replenishment.puml':
the source declares 'stock-replenishment', the index declares 'replenishment'.
Remove one of the two declarations.

Cost: two schemas, a documented precedence rule, and per-notation capability differences that must be stated rather than implied.

Decision

Adopt option C, introduced in the following order.

  1. Require a declared ID for published output. A --require-id option refuses to convert an input whose ID was not declared, removing the filename fallback from publishing pipelines while leaving ad-hoc local runs unaffected. No schema change.
  2. Verify model ID uniqueness in the aggregation repository. That is where artifacts from all sources are merged and therefore the only place where a cross-repository duplicate is observable. A converter cannot see the other repositories.
  3. Accept in-file IDs, PlantUML first, as the default source of identity, with the index able to pin a value and disagreement treated as an error. BPMN remains index-governed until a round-trip through the modellers in use is shown to preserve a custom <extensionElements> entry.

Ownership follows the same division: the author declares the value, the platform governs changes to it. This matches established practice for published namespaces — npm and Maven place the package name in the manifest the author edits and reject duplicates at publication; Backstage places metadata.name in catalog-info.yaml and enforces uniqueness at ingestion.

A model ID is stable across versions of the model. A version is metadata (version, dct:modified), not part of the IRI. Two versions that must coexist in the graph are two models with two IDs.

Effect on the emitted graph

None. The declaration site determines who maintains the value, not what is minted from it. All three options produce the same triples for the same IDs:

<https://example.org/la/bpmn/order-domain/graph/model> {
    <https://example.org/la/bpmn/order-domain> a arch:Model .
}
<https://example.org/la/bpmn/order-domain/graph/semantic> {
    <https://example.org/la/bpmn/order-domain/view/order-fulfillment>
        a arch:View , arch:Diagram , arch:ModelConcept ;
        arch:inModel <https://example.org/la/bpmn/order-domain> .
}

Step 1 of the decision changes what happens when no ID is declared. The filename is used unvalidated, so a source file named Order Flow.puml yields an identity nobody declared, with the space percent-encoded on serialisation:

# no declared ID: the filename becomes the identity
<https://example.org/la/plantuml/Order%20Flow>                a arch:Model .
<https://example.org/la/plantuml/Order%20Flow/graph/semantic> { … }

The rendered SVG for the same input is Order Flow.svg, so the published filename and the IRI differ in their encoding. Under --require-id the run fails instead:

[ERROR] Conversion failed: no model id declared for 'Order Flow.puml'. Declare one in the
diagram index, or pass --model-id.

Consequences

  • Identity can be declared alongside the diagram it identifies, while the index remains the record of what is published.
  • Two possible sources for one value require an explicit precedence rule and an error on disagreement.
  • Steps 1 and 2 stand on their own. If step 3 is not taken, the result is the current model plus a strict mode and a cross-repository check, with no schema change.
  • Per-notation capability differences become part of the documented contract and must appear on each converter page.
  • Changing a published identity is governed separately by ADR 0003: it fails the run unless declared under formerIds: or allowed with --allow-identity-change.

Open questions

  • Does a round-trip through the BPMN modellers in use preserve a custom <extensionElements> entry? Until this is answered, BPMN identity is declared in the index only.

Resolved during implementation

  • PlantUML declares identity in header comments ('!la-model:, '!la-view:) rather than in the name following @startuml. PlantUML gives that name its own meaning, as the output filename, and most sources omit it. Only the lines before @startuml are read, so an identity cannot be declared among the shapes it identifies.
  • A comment was also chosen over PlantUML's own preprocessor variables (!$view = "order-flow", dropping the leading '). That syntax is live: the preprocessor evaluates it before layout, so it would occupy a name the diagram's own !if defined($x) or styling logic might already use, for a feature meant to parameterize a diagram, not describe the file it lives in. A comment is inert to every renderer and cannot collide with anything the diagram does. Same reasoning as rejecting [[url]] and the <<$tag>> style-tag mechanism for extension data (see CHANGELOG).
  • --require-id governs the model ID. A view ID remains optional: without one, a source file is one model with one view, and the notation supplies the view ID.