Skip to content

Build Your Own Modeling Tool with Linked.Archi

The Build Your Own Metamodel guide ends with a section called "How a Modeling Tool Consumes This" — seven bullet points and a bootstrap query. This article picks up where that one stops. It shows how to build a working modeling tool that loads a Linked.Archi metamodel and provides a palette, canvas, validation, viewpoint filtering, document generation, and AI agent access.

The Cloud Platform Walkthrough provides the worked metamodel example. Everything here uses that metamodel as the running scenario.


What You Are Building

A Linked.Archi-aware modeling tool has seven components:

Modeling Tool Components

You do not need all seven on day one. The minimum viable tool is components 1–3. Add the rest incrementally.


Technology Choices

Two proven stacks, depending on your team:

Component TypeScript / Browser Python / Server
RDF store Oxigraph WASM or Comunica rdflib + Oxigraph (via oxrdflib)
SPARQL engine Oxigraph WASM or Comunica rdflib or Oxigraph server
SHACL validator rdf-validate-shacl pyshacl
Templating Handlebars.js pybars3 or Jinja2
UI framework React / Svelte / vanilla Streamlit / Flask / none (CLI)
Graph visualization D3.js / Cytoscape.js / ReactFlow NetworkX + matplotlib / Graphviz
MCP server @modelcontextprotocol/sdk (TypeScript) mcp (Python)

The Linked.Archi ecosystem uses both: the MCP server is TypeScript + Oxigraph, the static navigator is React + Comunica, and the validation pipeline is Java + RDF4J. Pick what fits your team.

This guide shows both stacks side by side where the patterns differ.


Component 1: Metamodel Loader

The metamodel manifest (arch:Metamodel) is the single entry point. Load it, follow the properties, and you have the complete modeling language.

The Bootstrap Query

PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?property ?value WHERE {
    ?metamodel a arch:Metamodel ;
               skos:prefLabel ?label .
    ?metamodel ?property ?value .
    VALUES ?property {
        arch:modelConcepts
        arch:formalRules
        arch:architectureViewpoints
        arch:conceptClassification
        arch:hasDeliverableTemplate
        arch:referenceData
        arch:presentationContextScheme
        arch:notationSet
        arch:derivationRules
        arch:referenceModels
        arch:architectureViewpointsRestrictions
    }
}

For the Cloud Platform example, this returns:

property value
arch:modelConcepts <https://meta.linked.archi/examples/cloudplatform/onto#>
arch:formalRules <https://meta.linked.archi/examples/cloudplatform/shapes#>
arch:architectureViewpoints <https://meta.linked.archi/examples/cloudplatform/viewpoints#>
arch:conceptClassification <https://meta.linked.archi/examples/cloudplatform/tax#>
arch:hasDeliverableTemplate cpdt:ServiceCatalog, cpdt:DeploymentGuide
arch:referenceData cprd:CloudPlatformReferenceData

TypeScript Implementation

import { Store, Parser } from 'n3';
import { QueryEngine } from '@comunica/query-sparql';

async function loadMetamodel(files: string[]): Promise<Store> {
  const store = new Store();
  const parser = new Parser();

  for (const file of files) {
    const ttl = await fs.readFile(file, 'utf-8');
    const quads = parser.parse(ttl);
    store.addQuads(quads);
  }
  return store;
}

async function bootstrap(store: Store) {
  const engine = new QueryEngine();
  const result = await engine.queryBindings(`
    PREFIX arch: <https://meta.linked.archi/core#>
    SELECT ?metamodel ?label ?property ?value WHERE {
      ?metamodel a arch:Metamodel ; skos:prefLabel ?label .
      ?metamodel ?property ?value .
      VALUES ?property {
        arch:modelConcepts arch:formalRules
        arch:architectureViewpoints arch:conceptClassification
        arch:hasDeliverableTemplate arch:referenceData
        arch:presentationContextScheme arch:notationSet
      }
    }
  `, { sources: [store] });

  const bindings = await result.toArray();
  // Group by property → build a MetamodelDescriptor
  return groupByProperty(bindings);
}

Python Implementation

from rdflib import Graph, Namespace

ARCH = Namespace("https://meta.linked.archi/core#")

def load_metamodel(files: list[str]) -> Graph:
    g = Graph()
    for f in files:
        g.parse(f, format="turtle")
    return g

def bootstrap(g: Graph) -> dict:
    query = """
    PREFIX arch: <https://meta.linked.archi/core#>
    PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
    SELECT ?metamodel ?label ?property ?value WHERE {
        ?metamodel a arch:Metamodel ; skos:prefLabel ?label .
        ?metamodel ?property ?value .
        VALUES ?property {
            arch:modelConcepts arch:formalRules
            arch:architectureViewpoints arch:conceptClassification
            arch:hasDeliverableTemplate arch:referenceData
            arch:presentationContextScheme arch:notationSet
        }
    }
    """
    results = g.query(query)
    return group_by_property(results)

What to Load

Load files in dependency order. The metamodel manifest imports the core ontology, which imports nothing. The ontology imports ArchiMate 4.0 (if extending it). Load them all into one graph:

core/core-onto.ttl                              ← always
modelingLanguages/archimate/4.0/archimate4-onto.ttl  ← if extending ArchiMate
examples/custom-metamodel/cloudplatform-onto.ttl      ← your metamodel
examples/custom-metamodel/cloudplatform-metamodel.ttl ← the manifest
examples/custom-metamodel/cloudplatform-tax.ttl       ← taxonomy
examples/custom-metamodel/cloudplatform-shapes.ttl    ← SHACL shapes
examples/custom-metamodel/cloudplatform-viewpoints.ttl
examples/custom-metamodel/cloudplatform-deliverable-templates.ttl
examples/custom-metamodel/cloudplatform-reference-data.ttl

Then load the user's model data on top:

examples/custom-metamodel/example-model.ttl      ← the actual architecture model

Component 2: Palette Builder

The palette is the list of element types and relationship types the user can place on the canvas. It comes entirely from the ontology — no hardcoding.

The Process: What to Use and Why

Building a palette requires three semantic assets, each serving a distinct role:

Semantic Asset File Pattern What It Gives the Palette
Ontology (*-onto.ttl) OWL classes + properties The actual element and relationship types — what the user can instantiate
Taxonomy (*-tax.ttl) SKOS concept scheme The grouping and ordering — how elements are organized in the palette tree
Notation (*-notation.ttl) VisualNotation + Style The visual appearance — shape, color, icon for each palette entry

The ontology tells you WHAT exists. The taxonomy tells you HOW TO GROUP IT. The notation tells you HOW TO DRAW IT.

Palette from Semantic Assets

Step-by-step palette generation

  1. Load the taxonomy — parse the SKOS ConceptScheme. The top-level skos:Concept entries become palette groups (e.g., "Business Layer", "Application Layer", "Runtime Services"). The skos:narrower links point to either sub-groups or leaf nodes (OWL class IRIs).

  2. Resolve leaf nodes to OWL classes — each leaf in the taxonomy's skos:narrower tree references an OWL class IRI from the ontology. This is the element type the user will instantiate on drag-drop. Exclude any class annotated with arch:isAbstractClass true — these are structural superclasses for validation/inference, not palette items.

  3. Enrich with ontology metadata — for each OWL class, read skos:prefLabel (display name), skos:definition (tooltip), and rdfs:subClassOf (for inheritance-based filtering and connection validation).

  4. Attach visual notation — query the active NotationSet for each element's VisualNotation. This gives you the shape, fill color, icon, and rendering mode to draw the palette entry.

  5. Discover relationship types — query all rdfs:subClassOf arch:QualifiedRelationship classes. Read their arch:domainIncludes / arch:rangeIncludes to know which connections are valid between which element types.

  6. Apply viewpoint filter (optional) — if a viewpoint is active, restrict the palette to only the types listed in arch:includesConcept.

The result is a fully data-driven palette where adding a new element type to the ontology + taxonomy automatically makes it appear in the tool — no code changes needed.


Discovering Element Types

The palette should only show concrete (instantiable) element types. Abstract classes — used for classification, validation, and inheritance — are marked with arch:isAbstractClass true and must be excluded:

PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?class ?label ?definition ?icon ?parent WHERE {
    ?class rdfs:subClassOf+ arch:Element ;
           skos:prefLabel ?label .
    FILTER NOT EXISTS { ?class arch:isAbstractClass true }
    OPTIONAL { ?class skos:definition ?definition }
    OPTIONAL { ?class arch:prefVisNotation ?icon }
    OPTIONAL { ?class rdfs:subClassOf ?parent .
               ?parent rdfs:subClassOf+ arch:Element }
    FILTER(lang(?label) = "en" || lang(?label) = "")
}
ORDER BY ?parent ?label

▶ Try it in the Playground

The FILTER NOT EXISTS { ?class arch:isAbstractClass true } line excludes structural superclasses like BehaviorElement, ActiveStructureElement, CommonDomainElement, etc. These exist for inheritance and SHACL validation but are never placed on a canvas by a user.

For the Cloud Platform metamodel, this returns Microservice, APIGateway, Container, KubernetesCluster, MessageBroker, EventTopic, ManagedDatabase, CacheStore, ObservabilityStack — plus all inherited ArchiMate 4.0 concrete types.

Discovering Relationship Types

PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?class ?label ?predicate ?sourceDomain ?targetRange WHERE {
    ?class rdfs:subClassOf arch:QualifiedRelationship ;
           skos:prefLabel ?label ;
           arch:unqualifiedForm ?predicate .
    OPTIONAL { ?predicate arch:domainIncludes ?sourceDomain }
    OPTIONAL { ?predicate arch:rangeIncludes ?targetRange }
    FILTER(lang(?label) = "en" || lang(?label) = "")
}

▶ Try it in the Playground

This tells the tool: "A Dependency can go from a Microservice to an ApplicationComponent." The tool uses this to enable/disable connection handles on the canvas.

Grouping by Taxonomy

The SKOS taxonomy provides palette groups. Elements link to taxonomy concepts via rdfs:seeAlso:

PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?group ?groupLabel ?class ?classLabel WHERE {
    ?class rdfs:seeAlso ?group .
    ?group a skos:Concept ;
           skos:prefLabel ?groupLabel .
    ?class skos:prefLabel ?classLabel .
    FILTER(lang(?groupLabel) = "en" || lang(?groupLabel) = "")
    FILTER(lang(?classLabel) = "en" || lang(?classLabel) = "")
}
ORDER BY ?groupLabel ?classLabel

▶ Try it in the Playground

Result for the Cloud Platform metamodel:

Group Elements
Runtime Services Microservice, APIGateway, ManagedDatabase, CacheStore
Integration MessageBroker, EventTopic
Infrastructure Container, KubernetesCluster
Observability ObservabilityStack

Building the Palette Data Structure

interface PaletteGroup {
  iri: string;
  label: string;
  elements: PaletteItem[];
}

interface PaletteItem {
  iri: string;        // e.g., cp:Microservice
  label: string;      // "Microservice"
  definition: string; // tooltip text
  icon: string;       // SVG URL from arch:prefVisNotation
  parentClass: string; // e.g., am4:ApplicationComponent
  // Visual notation (from NotationSet)
  renderingMode?: string; // "ShapeWithBadge" | "IconCentric" | "ShapeOnly"
  shapeType?: string;  // e.g., "RoundedRectangle"
  fillColor?: string;  // e.g., "#C9E7CB"
  lineColor?: string;  // e.g., "#000000"
  iconSymbol?: string; // glyph SVG URL (small badge or full icon depending on mode)
  iconPlacement?: string; // e.g., "TopRight" or "Center"
  width?: number;      // default width
  height?: number;     // default height
}

interface RelationshipType {
  iri: string;           // e.g., cp:Dependency
  label: string;         // "Dependency"
  predicate: string;     // cp:dependsOn
  validSources: string[]; // [cp:Microservice]
  validTargets: string[]; // [am4:ApplicationComponent]
  // Visual notation (from NotationSet)
  lineStyle?: string;    // e.g., "DashedLine"
  sourceDecor?: string;  // e.g., "NoDecoration"
  targetDecor?: string;  // e.g., "OpenArrowhead"
}

Loading Visual Notation from NotationSet

Instead of just reading arch:prefVisNotation (a single SVG URL), a full tool reads the NotationSet for structured rendering data:

PREFIX arch:     <https://meta.linked.archi/core#>
PREFIX arch-vis: <https://meta.linked.archi/core-vis#>
PREFIX skos:     <http://www.w3.org/2004/02/skos/core#>

# Get visual notation for all elements in the active notation set
SELECT ?concept ?label ?shape ?fill ?lineColor ?icon ?iconPos ?width ?height ?image ?renderMode
WHERE {
    ?vn a arch-vis:VisualNotation ;
        arch-vis:inNotationSet ?notationSet ;
        arch-vis:notationFor ?concept ;
        arch-vis:defaultStyle ?style .

    ?concept skos:prefLabel ?label .
    FILTER(lang(?label) = "en")

    ?style arch-vis:shapeType ?shape ;
           arch-vis:fillColor ?fill .
    OPTIONAL { ?style arch-vis:lineColor ?lineColor }
    OPTIONAL { ?style arch-vis:renderingMode ?renderMode }
    OPTIONAL { ?style arch-vis:iconSymbol ?icon }
    OPTIONAL { ?style arch-vis:iconPlacement ?iconPos }
    OPTIONAL { ?style arch-vis:defaultWidth ?width }
    OPTIONAL { ?style arch-vis:defaultHeight ?height }
    OPTIONAL { ?vn arch:prefVisNotation ?image }
}
ORDER BY ?label

Context-aware notation selection: To get the notation set for a specific audience:

PREFIX arch: <https://meta.linked.archi/core#>

# Find the notation set for the "Architect" presentation context
SELECT ?notationSet WHERE {
    ?context skos:prefLabel "Architect"@en ;
             arch:usesNotationSet ?notationSet .
}

If no context-specific set is found, fall back to arch:notationSet on the Metamodel.

For the full visual notation architecture, see the Visual Notation Guide.

Same Model, Different Notation — Vendor Icons vs ArchiMate

The NotationSet/PresentationContext system enables a powerful pattern: the same underlying model rendered with completely different visual styles depending on the audience.

## An architect sees standard ArchiMate notation (rounded rectangles + badges)
mypc:ArchitectContext
    arch:usesNotationSet am4not:ArchiMate40StandardNotation .

## A developer sees AWS Architecture Icons (icon-centric, 64px service icons)
mypc:DeveloperContext
    arch:usesNotationSet mynot:AWSIconNotation .

The AWS notation set uses arch-vis:IconCentric rendering mode — the icon IS the element:

:EC2Notation
    a                    arch-vis:VisualNotation ;
    arch-vis:notationFor myonto:EC2Instance ;
    arch-vis:inNotationSet :AWSIconNotation ;
    arch-vis:defaultStyle [
        arch-vis:renderingMode arch-vis:IconCentric ;
        arch-vis:iconSymbol    "https://d1.awsstatic.com/icons/Arch_Amazon-EC2_64.svg"^^xsd:anyURI ;
        arch-vis:iconPlacement arch-vis:Center ;
        arch-vis:fillColor     "#FFFFFF" ;
        arch-vis:defaultWidth  64.0 ;
        arch-vis:defaultHeight 64.0 ;
    ] .

The underlying RDF model data is identical in both views — only the visual rendering changes. This works for AWS, Azure, GCP, Kubernetes, or any vendor icon set. See the Visual Notation Guide — Vendor Icon Sets for the full pattern.

Filtering: Show Only Your Custom Types

If you extend ArchiMate 4.0 but only want to show your custom types in the palette (not all 55 ArchiMate types), filter by namespace:

PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?class ?label WHERE {
    ?class rdfs:subClassOf+ arch:Element ;
           skos:prefLabel ?label .
    FILTER(STRSTARTS(STR(?class), "https://meta.linked.archi/examples/cloudplatform/"))
}

Or show both, but visually separate them — custom types in the primary palette, inherited types in a collapsible "ArchiMate 4.0" section.

Palette Construction Conformance Specification

This section restates the palette behaviour as a normative specification. A tool that implements this section correctly will produce a conformant palette from any Linked.Archi metamodel without hardcoded knowledge of specific modeling languages.

Definitions

Term Meaning
Metamodel Manifest An instance of arch:Metamodel that aggregates all semantic assets for a modeling language.
Concrete Class An owl:Class that is rdfs:subClassOf+ either arch:Element or arch:QualifiedRelationship and does NOT carry the annotation arch:isAbstractClass true.
Abstract Class An owl:Class annotated with arch:isAbstractClass true. These exist for inheritance and constraint checking only — they MUST NOT appear in the palette.
Palette Group A skos:Concept in the taxonomy that has at least one skos:narrower link resolving (directly or transitively) to a Concrete Class.
Palette Entry A single draggable item in the palette UI, corresponding to one Concrete Class.
Active NotationSet The arch-vis:NotationSet resolved via the current PresentationContext (or the metamodel default).

Inputs

A conformant palette builder MUST accept exactly three semantic assets, resolved from the Metamodel Manifest:

Manifest Property Asset Type Provides
arch:modelConcepts OWL Ontology (*-onto.ttl) Element and relationship classes with labels, definitions, and hierarchy
arch:conceptClassification SKOS ConceptScheme (*-tax.ttl) Palette grouping and ordering via skos:narrower trees
arch:notationSet (or via PresentationContext) NotationSet (*-notation.ttl) Shape, colour, icon, and rendering mode per concept

Try the queries: Every SPARQL example below can be executed against live semantic assets in the SPARQL Playground — select a modeling language and click Execute. Each query has a ▶ Try it link that opens the playground pre-loaded with that query.

Algorithm

A conformant implementation MUST perform these steps in order. Steps 4 and 6 are OPTIONAL but RECOMMENDED.

Step 1 — Discover concrete element types.

Query all owl:Class instances that are rdfs:subClassOf+ arch:Element. Exclude any class where arch:isAbstractClass true is asserted. The arch:isAbstractClass annotation does NOT propagate through rdfs:subClassOf — only explicitly annotated classes are abstract.

Normative query:

PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?class ?label ?definition WHERE {
    ?class rdfs:subClassOf+ arch:Element ;
           skos:prefLabel ?label .
    FILTER NOT EXISTS { ?class arch:isAbstractClass true }
    OPTIONAL { ?class skos:definition ?definition }
    FILTER(lang(?label) = "en" || lang(?label) = "")
}

▶ Try it in the Playground

Example output (ArchiMate 3.2, first rows):

?class ?label ?definition
am:BusinessActor "Business Actor" "A business actor is a business entity that is capable of performing behavior."
am:BusinessProcess "Business Process" "A business process represents a sequence of business behaviors that achieves a specific outcome…"
am:ApplicationComponent "Application Component" "An encapsulation of application functionality aligned to implementation structure…"
am:Node "Node" "A computational or physical resource that hosts, manipulates, or interacts with other computational or physical resources."

Classes like am:BehaviorElement, am:ActiveStructureElement, and am:BusinessLayerElement do NOT appear because they carry arch:isAbstractClass true.

Step 2 — Discover concrete relationship types.

Query all owl:Class instances that are rdfs:subClassOf arch:QualifiedRelationship. For each, resolve arch:unqualifiedForm to the predicate, then read arch:domainIncludes and arch:rangeIncludes to determine valid source and target types.

Normative query:

PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?class ?label ?predicate ?sourceDomain ?targetRange WHERE {
    ?class rdfs:subClassOf arch:QualifiedRelationship ;
           skos:prefLabel ?label ;
           arch:unqualifiedForm ?predicate .
    OPTIONAL { ?predicate arch:domainIncludes ?sourceDomain }
    OPTIONAL { ?predicate arch:rangeIncludes ?targetRange }
    FILTER(lang(?label) = "en" || lang(?label) = "")
}

▶ Try it in the Playground

Example output (ArchiMate 3.2, selected rows):

?class ?label ?predicate ?sourceDomain ?targetRange
am:Composition "Composition" am:composedOf arch:Element arch:Element
am:Serving "Serving" am:serves arch:Element arch:Element
am:Realization "Realization" am:realizes arch:Element arch:Element
am:Triggering "Triggering" am:triggers am:BehaviorElement am:BehaviorElement

A tool uses ?sourceDomain / ?targetRange to enable or disable connection handles on the canvas — e.g., Triggering is only valid between behaviour elements.

Step 3 — Build palette groups from the SKOS taxonomy.

Walk the skos:narrower tree starting from the top concepts of the ConceptScheme. Each skos:Concept with at least one descendant that resolves to a Concrete Class becomes a Palette Group. The skos:prefLabel of the concept is the group label.

Leaf nodes in the taxonomy reference OWL class IRIs (from Step 1) via skos:narrower. These are the Palette Entries within that group.

The canonical taxonomy pattern (used by all Linked.Archi modeling languages):

@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix myonto: <https://meta.linked.archi/mymodel/onto#> .
@prefix :       <https://meta.linked.archi/mymodel/tax#> .

## Group concept — becomes a palette section header
:BusinessLayer
    a skos:Concept ;
    skos:prefLabel "Business Layer"@en ;
    skos:narrower myonto:BusinessActor, myonto:BusinessProcess,
                  myonto:BusinessService, myonto:BusinessObject ;
.

## The skos:narrower targets are OWL class IRIs from the ontology.
## A palette builder walks narrower to find what goes in each group.

Normative query for group membership (requires both the taxonomy and ontology loaded into the same graph):

PREFIX arch: <https://meta.linked.archi/core#>
PREFIX owl:  <http://www.w3.org/2002/07/owl#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

# Convention: skos:narrower is used ONLY at the boundary from taxonomy concept → OWL class.
#             Taxonomy-internal hierarchy uses skos:broader (child → parent) exclusively.
# Taxonomy provides: ?group (skos:Concept with skos:narrower → OWL class IRIs)
# Ontology provides: ?class a owl:Class, skos:prefLabel, arch:isAbstractClass
SELECT ?group ?groupLabel ?class ?classLabel WHERE {
    ?group a skos:Concept ;
           skos:narrower ?class ;
           skos:prefLabel ?groupLabel .
    ?class a owl:Class ;
           skos:prefLabel ?classLabel .
    FILTER NOT EXISTS { ?class arch:isAbstractClass true }
    FILTER(lang(?groupLabel) = "en" || lang(?groupLabel) = "")
    FILTER(lang(?classLabel) = "en" || lang(?classLabel) = "")
}
ORDER BY ?groupLabel ?classLabel

▶ Try it in the Playground

To build a nested palette tree (e.g., "Element by Layer" → "Business Layer" → items), walk skos:broader upward from each group:

PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

# Get the ancestor chain for palette nesting
SELECT ?group ?groupLabel ?parent ?parentLabel WHERE {
    ?group a skos:Concept ;
           skos:broader ?parent ;
           skos:prefLabel ?groupLabel .
    ?parent skos:prefLabel ?parentLabel .
    FILTER(lang(?groupLabel) = "en" || lang(?groupLabel) = "")
    FILTER(lang(?parentLabel) = "en" || lang(?parentLabel) = "")
}

Example output (ArchiMate 3.2, first rows):

?group ?groupLabel ?class ?classLabel
amtax:BusinessLayer "Business Layer" am:BusinessActor "Business Actor"
amtax:BusinessLayer "Business Layer" am:BusinessCollaboration "Business Collaboration"
amtax:BusinessLayer "Business Layer" am:BusinessProcess "Business Process"
amtax:ApplicationLayer "Application Layer" am:ApplicationComponent "Application Component"
amtax:ApplicationLayer "Application Layer" am:DataObject "Data Object"
amtax:TechnologyLayer "Technology Layer" am:Node "Node"
amtax:TechnologyLayer "Technology Layer" am:Artifact "Artifact"

This is the data needed to render a grouped palette: each ?groupLabel becomes a section header, each ?classLabel becomes a draggable item within that section.

Alternative linking: some metamodels additionally use rdfs:seeAlso from individual leaf concepts to OWL classes. A conformant tool MUST support both skos:narrower (group → class, the primary pattern) and rdfs:seeAlso (concept → class, legacy/supplementary). When both are present, skos:narrower takes precedence for building the palette tree.

Step 4 — Attach visual notation (RECOMMENDED).

For each Concrete Class, query the Active NotationSet for a arch-vis:VisualNotation where arch-vis:notationFor matches the class IRI. Extract the arch-vis:defaultStyle to obtain shape, colour, icon, and rendering mode.

Normative query:

PREFIX arch-vis: <https://meta.linked.archi/core-vis#>
PREFIX arch:     <https://meta.linked.archi/core#>
PREFIX skos:     <http://www.w3.org/2004/02/skos/core#>

SELECT ?class ?shape ?fillColor ?lineColor ?iconSymbol ?iconPlacement
       ?renderingMode ?width ?height ?compositeImage WHERE {
    ?vn a arch-vis:VisualNotation ;
        arch-vis:notationFor ?class ;
        arch-vis:inNotationSet ?activeNotationSet ;
        arch-vis:defaultStyle ?style .
    ?style arch-vis:shapeType ?shape ;
           arch-vis:fillColor ?fillColor .
    OPTIONAL { ?style arch-vis:lineColor ?lineColor }
    OPTIONAL { ?style arch-vis:iconSymbol ?iconSymbol }
    OPTIONAL { ?style arch-vis:iconPlacement ?iconPlacement }
    OPTIONAL { ?style arch-vis:renderingMode ?renderingMode }
    OPTIONAL { ?style arch-vis:defaultWidth ?width }
    OPTIONAL { ?style arch-vis:defaultHeight ?height }
    OPTIONAL { ?vn arch:prefVisNotation ?compositeImage }
}

▶ Try it in the Playground

Example output (ArchiMate 3.2, selected rows):

?class ?shape ?fillColor ?lineColor ?iconSymbol ?iconPlacement ?renderingMode ?width ?height
am:BusinessActor arch-vis:RoundedRectangle "#FFFFB5" "#000000" …/glyphs/business-actor.svg arch-vis:TopRight arch-vis:ShapeWithBadge 120.0 55.0
am:ApplicationComponent arch-vis:RoundedRectangle "#C9E7CB" "#000000" …/glyphs/application-component.svg arch-vis:TopRight arch-vis:ShapeWithBadge 120.0 55.0
am:Node arch-vis:RoundedRectangle "#C9E7CB" "#000000" …/glyphs/node.svg arch-vis:TopRight arch-vis:ShapeWithBadge 120.0 55.0

This gives the tool everything it needs to render each palette entry: the geometric shape, fill/line colours, which glyph to badge in the corner, and the default dimensions.

Fallback: if no VisualNotation is found for a class, the tool SHOULD fall back to arch:prefVisNotation on the class itself (a single SVG image). If that is also absent, render a plain rectangle with the class label.

Step 5 — Resolve the Active NotationSet.

The Active NotationSet is determined as follows (in priority order):

  1. If a PresentationContext is active, use the value of arch:usesNotationSet on that context.
  2. Otherwise, use the value of arch:notationSet on the Metamodel Manifest.
  3. If neither is present, skip visual notation (palette entries render as plain text).

Step 6 — Apply viewpoint filter (OPTIONAL).

If a viewpoint is active, restrict the palette to classes listed in arch:includesConcept on the viewpoint. The tool MUST also include subclasses of listed classes (i.e., if the viewpoint includes am4:ApplicationComponent and cp:Microservice rdfs:subClassOf am4:ApplicationComponent, Microservice is allowed).

Conformance Requirements

A tool claiming conformance to this specification:

ID Requirement Level
PAL-1 MUST exclude classes annotated with arch:isAbstractClass true from the palette. Required
PAL-2 MUST derive palette entries solely from owl:Class instances in the loaded ontology — no hardcoded type lists. Required
PAL-3 MUST group palette entries according to the SKOS taxonomy hierarchy when a taxonomy is present. Required
PAL-4 MUST support both skos:narrower (concept → class) and rdfs:seeAlso (class → concept) linking patterns for group membership. Required
PAL-5 SHOULD render palette entries using the Active NotationSet's arch-vis:defaultStyle when available. Recommended
PAL-6 SHOULD support all three rendering modes (ShapeWithBadge, IconCentric, ShapeOnly). Recommended
PAL-7 MUST support viewpoint-based palette filtering when a viewpoint is active. Required
PAL-8 MUST NOT require code changes to add new element types — adding a class to the ontology and taxonomy is sufficient. Required
PAL-9 MUST read relationship connectivity rules (arch:domainIncludes, arch:rangeIncludes) and expose them to the canvas for connection validation. Required
PAL-10 SHOULD fall back gracefully when optional assets (notation, taxonomy) are absent — elements remain usable, just unstyled or ungrouped. Recommended

Verification

To verify conformance, load the ArchiMate 3.2 metamodel (modelingLanguages/archimate/3.2/archimate3.2-metamodel.ttl and its dependencies) and confirm:

  1. The palette contains exactly the concrete element types (no abstract classes like BehaviorElement or StrategyLayerElement).
  2. Elements are grouped by layer/aspect as defined in archimate3.2-tax.ttl.
  3. Each element entry displays the correct shape, fill colour, and icon glyph from archimate3.2-notation.ttl.
  4. Relationship types show correct line styles and endpoint decorations.
  5. Activating a viewpoint restricts the palette to only the concepts listed in that viewpoint.

Component 3: Canvas / Editor

The canvas is where users place elements and draw relationships. This is the most UI-intensive component and the one where technology choice matters most.

Library Strengths Best For
ReactFlow React-native, handles, minimap, built-in layout Web-based tools with React
Cytoscape.js Graph-focused, many layouts, extensions Graph-heavy tools
D3.js Full control, force layouts Custom visualizations
JointJS Diagramming-focused, ports, links Traditional diagramming tools

Creating Model Data

When a user drags a Microservice from the palette onto the canvas, the tool creates RDF triples:

@prefix cp:   <https://meta.linked.archi/examples/cloudplatform/onto#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix :     <https://model.example.com/my-platform#> .

:OrderService a cp:Microservice ;
    skos:prefLabel "Order Service"@en ;
    cp:resilienceLevel "critical" ;
    cp:deploymentStrategy "blue-green" ;
    cp:serviceTier "tier-1" .

Creating Relationships

When a user draws an edge from OrderService to PaymentService, the tool creates both the direct triple and the qualified relationship:

# Direct triple — for graph traversal
:OrderService cp:dependsOn :PaymentService .

# Qualified relationship — for metadata and management
:OrderService cp:qualifiedDependsOn :dep-order-payment .
:dep-order-payment a cp:Dependency ;
    arch:source :OrderService ;
    arch:target :PaymentService ;
    skos:prefLabel "Order → Payment dependency"@en .

Connection Validation

Before allowing a user to complete an edge, check whether the relationship type permits that source→target combination. Use the arch:domainIncludes / arch:rangeIncludes data from the palette query:

function canConnect(
  sourceType: string,
  targetType: string,
  relationshipType: RelationshipType,
  classHierarchy: Map<string, string[]>  // class → superclasses
): boolean {
  const sourceValid = relationshipType.validSources.some(
    s => sourceType === s || classHierarchy.get(sourceType)?.includes(s)
  );
  const targetValid = relationshipType.validTargets.some(
    t => targetType === t || classHierarchy.get(targetType)?.includes(t)
  );
  return sourceValid && targetValid;
}

This uses the class hierarchy — a Microservice is a valid source for any relationship that accepts ApplicationComponent, because cp:Microservice rdfs:subClassOf am4:ApplicationComponent.

Property Panel

When the user selects an element, show a property panel. The properties come from the ontology:

PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?property ?label ?range WHERE {
    ?property arch:domainIncludes ?elementType ;
              skos:prefLabel ?label .
    OPTIONAL { ?property rdfs:range ?range }
    FILTER(lang(?label) = "en" || lang(?label) = "")
}

For cp:Microservice, this returns resilienceLevel, deploymentStrategy, serviceTier — each with its datatype. The SHACL shapes provide the allowed values (sh:in) for dropdowns.

Extracting Allowed Values from SHACL

PREFIX sh:   <http://www.w3.org/ns/shacl#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?property ?allowedValue WHERE {
    ?shape sh:targetClass ?elementType ;
           sh:property ?propShape .
    ?propShape sh:path ?property ;
               sh:in/rdf:rest*/rdf:first ?allowedValue .
}

For cp:resilienceLevel on cp:Microservice, this returns: "critical", "high", "medium", "low" — ready for a dropdown.


Component 4: Live SHACL Validation

SHACL shapes turn the metamodel's rules into real-time feedback — red squiggles when a required property is missing, warnings when a relationship has an invalid target.

How It Works

  1. The user edits the model (adds an element, changes a property, draws an edge).
  2. The tool serializes the current model graph.
  3. The SHACL engine validates the model graph against the shapes graph.
  4. Validation results are mapped back to canvas elements as errors/warnings.

TypeScript: rdf-validate-shacl

import SHACLValidator from 'rdf-validate-shacl';
import { DataFactory } from 'n3';

async function validate(
  dataStore: Store,
  shapesStore: Store
): Promise<ValidationResult[]> {
  const validator = new SHACLValidator(shapesStore, { factory: DataFactory });
  const report = validator.validate(dataStore);

  return report.results.map(r => ({
    focusNode: r.focusNode?.value,       // the element IRI
    path: r.path?.value,                  // the property that failed
    message: r.message?.[0]?.value,       // human-readable message
    severity: r.severity?.value,          // sh:Violation, sh:Warning, sh:Info
    sourceShape: r.sourceShape?.value,    // which shape triggered this
  }));
}

Python: pyshacl

from pyshacl import validate as shacl_validate

def validate(data_graph, shapes_graph):
    conforms, results_graph, results_text = shacl_validate(
        data_graph,
        shacl_graph=shapes_graph,
        inference='none',
        abort_on_first=False,
    )
    # Parse results_graph for individual violations
    return parse_validation_results(results_graph)

Mapping Results to the Canvas

Each validation result has a focusNode — the IRI of the element that failed. Map this back to the canvas node and show the error:

function applyValidationResults(
  canvas: CanvasState,
  results: ValidationResult[]
) {
  // Clear previous markers
  canvas.clearAllMarkers();

  for (const result of results) {
    const node = canvas.findNodeByIRI(result.focusNode);
    if (!node) continue;

    if (result.severity.endsWith('Violation')) {
      node.addMarker('error', result.message);
    } else if (result.severity.endsWith('Warning')) {
      node.addMarker('warning', result.message);
    } else {
      node.addMarker('info', result.message);
    }
  }
}

When to Validate

  • On every edit — if the model is small (< 500 elements). SHACL validation is fast for small graphs.
  • On save — if the model is large. Debounce to avoid blocking the UI.
  • On demand — a "Validate" button that runs the full check.

For the CI/CD pipeline, the existing .scripts/validate.sh handles batch validation using RDF4J:

.scripts/validate.sh --shacl example-model.ttl cloudplatform-shapes.ttl

What the Cloud Platform Shapes Catch

The cloudplatform-shapes.ttl enforces rules like:

  • Every Microservice must have a skos:prefLabel, resilienceLevel
  • resilienceLevel must be one of: critical, high, medium, low
  • deploymentStrategy must be one of: blue-green, canary, rolling, recreate
  • Every Container must have a containerImage and positive replicaCount
  • Every Dependency must have arch:source and arch:target
  • Critical microservices must be tier-1 (conditional rule)

Component 5: Viewpoint Filter

Viewpoints restrict which element and relationship types are visible in a given view. When the user opens a "Service Dependency View", only the relevant types appear in the palette and on the canvas.

Discovering Viewpoints

PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?viewpoint ?label ?definition ?viewType WHERE {
    ?viewpoint a arch:Viewpoint ;
               skos:prefLabel ?label .
    OPTIONAL { ?viewpoint skos:definition ?definition }
    OPTIONAL { ?viewpoint arch:viewType ?viewType }
    FILTER(lang(?label) = "en" || lang(?label) = "")
}

For the Cloud Platform metamodel, this returns four viewpoints: Service Dependency, Deployment, Data Flow, Platform Overview.

Getting Allowed Concepts for a Viewpoint

PREFIX arch: <https://meta.linked.archi/core#>

SELECT ?concept WHERE {
    <viewpoint-iri> arch:includesConcept ?concept .
}

The Service Dependency viewpoint includes: cp:Microservice, cp:APIGateway, am4:ApplicationComponent, cp:Dependency. Only these types appear in the palette when this viewpoint is active.

Filtering the Palette

function filterPalette(
  fullPalette: PaletteGroup[],
  allowedConcepts: Set<string>,
  classHierarchy: Map<string, string[]>
): PaletteGroup[] {
  return fullPalette
    .map(group => ({
      ...group,
      elements: group.elements.filter(el =>
        allowedConcepts.has(el.iri) ||
        classHierarchy.get(el.iri)?.some(parent => allowedConcepts.has(parent))
      ),
    }))
    .filter(group => group.elements.length > 0);
}

Filtering the Canvas

Elements already on the canvas that don't belong to the active viewpoint can be dimmed or hidden:

function applyViewpointFilter(
  canvas: CanvasState,
  allowedConcepts: Set<string>
) {
  for (const node of canvas.nodes) {
    const isAllowed = allowedConcepts.has(node.type) ||
      classHierarchy.get(node.type)?.some(p => allowedConcepts.has(p));
    node.setOpacity(isAllowed ? 1.0 : 0.2);
  }
}

Viewpoint Metadata for the UI

Viewpoints also carry stakeholder and concern information — useful for a viewpoint picker:

PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?viewpoint ?vpLabel ?stakeholder ?shLabel ?concern ?cLabel WHERE {
    ?viewpoint a arch:Viewpoint ;
               skos:prefLabel ?vpLabel .
    OPTIONAL {
        ?viewpoint arch:targetsStakeholder ?stakeholder .
        ?stakeholder skos:prefLabel ?shLabel .
    }
    OPTIONAL {
        ?viewpoint arch:viewpointFramesConcern ?concern .
        ?concern skos:prefLabel ?cLabel .
    }
}

This lets you build a viewpoint picker that says: "Service Dependency View — for Developers and Architects — addresses Dependency Management concern."


Component 6: Document Generator

The document generation pipeline is fully declarative — the ontology defines what to extract, how to structure it, and how to render it. The tool contains no domain-specific logic.

For the full specification, see the Deliverable Generator Spec. This section shows how to implement it.

The Pipeline

Deliverable Pipeline

Step 1: Discover Template Sections

PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?section ?order ?title ?resultKey ?query WHERE {
    <template-iri> arch:templateHasSection ?section .
    ?section arch:sectionOrder ?order ;
             skos:prefLabel ?title ;
             arch:sectionResultKey ?resultKey ;
             arch:sectionQuery ?query .
}
ORDER BY ?order

Step 2: Execute Queries and Assemble Context

import Handlebars from 'handlebars';

async function generateDocument(
  templateIRI: string,
  store: Store,
  meta: Record<string, string>
): Promise<string> {
  // 1. Get sections
  const sections = await querySections(store, templateIRI);

  // 2. Execute each section's SPARQL query
  const context: Record<string, any> = { meta };
  for (const section of sections) {
    const results = await executeSPARQL(store, section.query);
    context[section.resultKey] = results;
  }

  // 3. Load and render the Handlebars template
  const templatePath = await getTemplateResource(store, templateIRI);
  const templateSource = await fs.readFile(templatePath, 'utf-8');
  const template = Handlebars.compile(templateSource);

  return template(context);
}

Step 3: Render

The Cloud Platform metamodel includes two deliverable templates:

Service Catalog — lists all microservices with their properties:

# Service Catalog

Generated: {{meta.date}}

## Microservices

| Service | Resilience | Deployment | Tier |
|---------|-----------|------------|------|
{{#each microservices}}
| {{this.label}} | {{this.resilience}} | {{this.deployment}} | {{this.tier}} |
{{/each}}

## Dependencies

| Source | Target | Description |
|--------|--------|-------------|
{{#each dependencies}}
| {{this.sourceLabel}} | {{this.targetLabel}} | {{this.description}} |
{{/each}}

Deployment Guide — infrastructure topology:

# Deployment Guide

## Kubernetes Clusters

{{#each clusters}}
### {{this.label}}

- Region: {{this.region}}
- Node count: {{this.nodeCount}}

#### Containers

| Container | Image | Replicas |
|-----------|-------|----------|
{{#each this.containers}}
| {{this.label}} | {{this.image}} | {{this.replicas}} |
{{/each}}

{{/each}}

Output Formats

The arch:templateHasFormat property declares supported formats. For Markdown → HTML, use a Markdown renderer. For Markdown → PDF, pipe through Pandoc or WeasyPrint:

# Markdown → HTML
npx marked service-catalog.md -o service-catalog.html

# Markdown → PDF
pandoc service-catalog.md -o service-catalog.pdf

Component 7: AI Agent Access (MCP)

The Model Context Protocol (MCP) exposes the knowledge graph to AI agents. The Linked.Archi MCP server (ai-enablement/linked-archi-mcp/) provides a reference implementation using TypeScript + Oxigraph.

What the MCP Server Exposes

Tool What it does
query-graph Execute arbitrary SPARQL against the model
list-elements List all elements with type and label
get-element Get full details for one element
get-relationships Get all relationships for an element
graph-stats Summary statistics (element counts by type, relationship counts)
Prompt What it does
impact-analysis Trace how a change to one element propagates
architecture-overview Generate a natural-language summary of the model
explore-ontology Explain the metamodel structure

Building Your Own MCP Server

If your modeling tool has its own graph store, you can expose it via MCP:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new McpServer({
  name: 'my-architecture-tool',
  version: '1.0.0',
});

server.tool('query-graph', { query: z.string() }, async ({ query }) => {
  const results = await sparqlEngine.query(query);
  return { content: [{ type: 'text', text: JSON.stringify(results) }] };
});

server.tool('list-elements', {}, async () => {
  const results = await sparqlEngine.query(`
    PREFIX arch: <https://meta.linked.archi/core#>
    PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
    SELECT ?element ?label ?type WHERE {
      ?element a ?type ; skos:prefLabel ?label .
      ?type rdfs:subClassOf+ arch:Element .
    }
  `);
  return { content: [{ type: 'text', text: JSON.stringify(results) }] };
});

const transport = new StdioServerTransport();
await server.connect(transport);

Why This Matters

With MCP, an AI agent can:

  • "What microservices depend on the Payment Service?" → query-graph with a SPARQL query
  • "Show me all critical services without a deployment strategy" → query-graph with a SHACL-like filter
  • "Generate an impact analysis for removing the Order Database" → impact-analysis prompt
  • "What's the overall health of this architecture?" → architecture-overview prompt

The agent queries the same graph the tool uses — no separate export or sync needed.


Presentation Contexts: Stakeholder-Specific Rendering

Presentation contexts are SKOS concepts that define how the same model looks for different audiences. They are not a separate component — they influence how components 2, 3, 5, and 6 behave.

How They Work

The metamodel declares a arch:presentationContextScheme — a SKOS ConceptScheme with concepts like Executive, Architect, Developer, Operations, Governance.

Each context implies:

Aspect Executive Developer Architect
Palette Business elements only Application + Technology All domains
Detail level Labels only Full properties Full properties + relationships
Viewpoints Strategy, Capability Service Dependency, Deployment All viewpoints
Documents Executive Summary Service Catalog, Deployment Guide Architecture Definition
Visual style Simplified icons, large labels Technical icons, code-style Full ArchiMate notation

Querying Presentation Contexts

PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?context ?label ?definition WHERE {
    ?context skos:topConceptOf ?scheme ;
             skos:prefLabel ?label ;
             skos:definition ?definition .
    FILTER(lang(?label) = "en" || lang(?label) = "")
}

Applying a Context

The tool reads the context's skos:definition to determine filtering rules. A more structured approach adds custom properties to each context:

:DeveloperContext
    a skos:Concept ;
    skos:prefLabel "Developer / Engineering"@en ;
    arch:contextIncludesDomain :ApplicationDomain, :TechnologyDomain ;
    arch:contextDefaultViewpoint :ServiceDependencyViewpoint ;
    arch:contextDetailLevel "full" ;
    arch:contextDeliverableTemplate :ServiceCatalog, :DeploymentGuide .

The tool queries these properties and adjusts the palette, default viewpoint, and available documents accordingly.


Putting It All Together: The Minimal Viable Tool

Here is the simplest possible tool — a CLI that loads a metamodel, validates a model, and generates a document:

// minimal-tool.ts
import { Store, Parser } from 'n3';
import SHACLValidator from 'rdf-validate-shacl';
import Handlebars from 'handlebars';
import { QueryEngine } from '@comunica/query-sparql';

async function main() {
  const store = new Store();
  const parser = new Parser();
  const engine = new QueryEngine();

  // 1. Load metamodel + model
  const files = [
    'core/core-onto.ttl',
    'examples/custom-metamodel/cloudplatform-onto.ttl',
    'examples/custom-metamodel/cloudplatform-metamodel.ttl',
    'examples/custom-metamodel/cloudplatform-shapes.ttl',
    'examples/custom-metamodel/cloudplatform-deliverable-templates.ttl',
    'examples/custom-metamodel/example-model.ttl',
  ];
  for (const file of files) {
    const ttl = await Bun.file(file).text();
    store.addQuads(parser.parse(ttl));
  }

  // 2. Validate
  const shapesStore = new Store();
  const shapesTtl = await Bun.file(
    'examples/custom-metamodel/cloudplatform-shapes.ttl'
  ).text();
  shapesStore.addQuads(parser.parse(shapesTtl));

  const validator = new SHACLValidator(shapesStore);
  const report = validator.validate(store);
  if (!report.conforms) {
    console.error('Validation errors:');
    for (const r of report.results) {
      console.error(`  ${r.focusNode?.value}: ${r.message?.[0]?.value}`);
    }
  } else {
    console.log('Model is valid.');
  }

  // 3. Generate document
  const queryResult = await engine.queryBindings(`
    PREFIX cp:   <https://meta.linked.archi/examples/cloudplatform/onto#>
    PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
    SELECT ?label ?resilience ?deployment ?tier WHERE {
      ?ms a cp:Microservice ;
          skos:prefLabel ?label ;
          cp:resilienceLevel ?resilience .
      OPTIONAL { ?ms cp:deploymentStrategy ?deployment }
      OPTIONAL { ?ms cp:serviceTier ?tier }
    } ORDER BY ?label
  `, { sources: [store] });

  const microservices = (await queryResult.toArray()).map(b => ({
    label: b.get('label')?.value,
    resilience: b.get('resilience')?.value,
    deployment: b.get('deployment')?.value ?? '—',
    tier: b.get('tier')?.value ?? '—',
  }));

  const template = Handlebars.compile(`# Service Catalog

| Service | Resilience | Deployment | Tier |
|---------|-----------|------------|------|
{{#each microservices}}
| {{this.label}} | {{this.resilience}} | {{this.deployment}} | {{this.tier}} |
{{/each}}
`);

  const output = template({ microservices });
  console.log(output);
}

main();

Run it:

npx tsx minimal-tool.ts

Output:

# Service Catalog

| Service | Resilience | Deployment | Tier |
|---------|-----------|------------|------|
| API Gateway | high | blue-green | tier-1 |
| Inventory Service | medium | rolling | tier-2 |
| Order Service | critical | blue-green | tier-1 |
| Payment Service | critical | canary | tier-1 |
| Shipping Service | medium | rolling | tier-2 |

From here, add a web UI (ReactFlow for the canvas, a sidebar for the palette, a panel for properties) and you have a visual modeling tool.


Incremental Build Path

Phase Components Effort What You Get
1. CLI Loader + Validator + Generator 1–2 days Validate models and generate documents from the command line
2. Browser + Palette + Canvas (ReactFlow) 1–2 weeks Visual editor with drag-and-drop elements and edges
3. Smart Editor + Viewpoint Filter + Property Panel 1 week Viewpoint-filtered palette, property editing with dropdowns from SHACL
4. Full Tool + Presentation Contexts + MCP 1 week Stakeholder-specific views and AI agent integration

Each phase produces a usable tool. You don't need to build everything before shipping.


How This Relates to Existing Linked.Archi Tools

The Linked.Archi toolbox already implements several of these components:

Existing Tool Components It Implements
Validation Script (Java + RDF4J) SHACL Validator (batch mode)
Deliverable Generator Document Generator (full pipeline spec)

A new modeling tool can reuse these directly or use them as reference implementations.


References