This is the eighth post in the series. The first five built the infrastructure: the ontology, the design decisions, the derivation engine, the extensibility mechanism, and the enforcement layer. The two that followed corrected it: what enforcement actually costs, and the direct/derived distinction Appendix B had been carrying all along. This post puts that infrastructure to work. SPARQL becomes the query language for architecture, and the interesting result is which questions it answers well and which it only appears to. Every query below runs against the ArchiSurance 3.1 case study loaded into GraphDB as RDF, and every number below came out of that repository rather than out of my head, including the ones that made me delete a section.
The Repository Is Ready to Answer Your Questions
On post 1 I framed three problems and I proposed one solution: Building ArchiMate in RDF. Seven posts later, the ontology is built and available on GitHub as v2-beta. The ontology carries the semantics. The derivation engine materializes the red lines. The validation layers enforce the metamodel. Everything that needed to be in place for the graph to be trustworthy is in place.
One of the three problems gets answered here, and a second gets a straight look at what it costs. “The repository should answer questions on demand” is what the query patterns and impact analysis sections demonstrate. The third “the system should match how the organization actually works” is federation, which I promised here and am moving to its own post; there is a section near the end explaining why. The first problem, the diagrams dying on a shelf, is harder still. Keeping a model current, making it a living twin of the enterprise rather than a snapshot of it, needs automated extraction from source code and infrastructure.
For now, let’s look at our test model and see what the graph can do.
Our Test Model: ArchiSurance
Every query in this post runs against a real model: The Open Group ArchiSurance case study which is a fictitious insurance company modeled across all layers, from strategy and motivation through business processes, application services, and technology infrastructure. It is the closest thing the ArchiMate ecosystem has to a standard test dataset.
The model ships as an ArchiMate Model Exchange File (XML). To load it into the RDF graph, I used Ron Deen’s ArchiMate RDF to XML adapter to convert between the exchange format and the canonical RDF representation. The result: ArchiSurance-in-RDF
361 elements across 43 types, from ValueStream and Capability at the strategy layer, through BusinessProcess and ApplicationComponent in the core, down to Device and SystemSoftware in technology. 600 asserted relationships, the edges someone drew: composition, aggregation, assignment, realization, serving, triggering, flow, and the rest. The source model actually carries 610; ten of them are association relationships whose target is another relationship rather than an element, and the canonical RDF form has no addressable resource for that yet, so they did not survive the conversion. 2,197 derived relationships, the red lines the derivation engine from post 3 materialized by firing DR1 through DR8 and junction transparency to a fixpoint, under the restrictions Appendix B.4 places on applying them. Three and a half times the relationship data in the original model.
If you want to follow along, the exchange file is available from The Open Group’s ArchiSurance downloads page, and the converted RDF is at ArchiSurance-in-RDF.
Setup lives at the end of this post: building the repository, loading the ontology, getting ArchiSurance in, and running the derivation rules that produce every number below. Read it before you try to reproduce anything. There are two ways to end up with a repository that looks entirely correct and is silently doing nothing.
The result is the entire argument of this series expressed as a ratio. ArchiSurance asserts 2,345 triples: 361 elements and 600 hand-drawn relationships. Run the valid Appendix B rules to a fixpoint and the repository holds 44,105 triples, carrying 2,197 derived relationships, every one of them marked archimate:confidence archimate:ValidDerivation and traceable through archimate:derivedFrom to the asserted relationships it came from.
Six hundred relationships were drawn by a human. Two thousand more were entailed by them and nobody had ever seen them. That is the gap between a diagram and a model.
SPARQL: One Query Language, Three Kinds of Facts
SPARQL is the W3C standard query language for RDF. If you have worked with SQL, the structure will feel familiar: SELECT, WHERE, FILTER, ORDER BY. The difference is that instead of querying tables and columns, you query triples: subject, predicate, object. Every fact in the graph is a triple, and every SPARQL query is a pattern of triples with variables in place of the parts you want to find.
What makes SPARQL interesting for ArchiMate in RDF is that the graph contains three kinds of facts, and a single query can traverse all three:
- Asserted facts — relationships that someone explicitly drew. A
BusinessActorassigned to aBusinessProcess. AnApplicationComponentrealizing anApplicationService. These are the facts a modeler put into the graph. - Derived facts — relationships the derivation engine materialized from post 3. The red line. A
Nodeserving aBusinessServicethrough a chain of realizations and assignments that no one drew directly. These carryarchimate:confidence archimate:ValidDerivationorarchimate:PotentialDerivationas RDF-Star metadata. - Inferred facts — classifications that OWL’s entailment regime produces. A
BusinessActoris first an ArchiMateConceptof typeElement, and any instance is automatically anActiveStructureelement and aBusinessLayerelement, because the subclass chain propagates. No one asserted this. The reasoner did.
The third kind is the easiest to wave at and the hardest to picture, so here it is on one element. ArchiSurance asserts that Web Portal is an ApplicationComponent. One statement, written once, in the exchange file. This is what the repository holds after the reasoner has finished with it:
| Web Portal (is a) | where it came from |
|---|---|
ApplicationComponent | asserted |
InternalActiveStructure | inferred |
ActiveStructure | inferred |
StructureAspect | inferred |
ApplicationLayer | inferred |
CoreLayer | inferred |
Layer | inferred |
CoreDomain | inferred |
Domain | inferred |
Aspect | inferred |
Element | inferred |
Concept | inferred |
ArchiMate | inferred |
Twelve statements nobody wrote. They follow from rdfs:subClassOf in the ontology and from rdf:type being transitive over it, and they are the reason the inventory query below can ask for everything in the business layer without naming BusinessActor, BusinessProcess or any other concrete type.
Three of those twelve are worth separating out, because they are three different questions the ontology answers about the same element. InternalActiveStructure is what kind of thing it is. ApplicationLayer is where it sits. CoreDomain is which group the derivation restrictions put it in, and that last one is not derivable from the other two: the Core domain takes in the physical elements, locations and groupings that the layer hierarchy keeps separate. Three classification axes, cutting across each other, and every element lands on all three without anyone typing them.
Relationships get the same treatment. Home & Away Policy Administration realization Policy Registration is one asserted triple, and the repository holds two: the realization somebody drew, and a structuralRelationship the reasoner added because the ontology declares one a subproperty of the other. That is what lets a query ask for structural relationships without listing composition, aggregation, assignment and realization by hand.
A single SPARQL query can walk all three kinds without distinguishing them. That is the point. The graph holds more than anyone drew.
Sometimes you want the distinction back, and standard SPARQL has no way to ask for it. A triple is a triple; nothing in the query language says where it came from. GraphDB answers this with two pseudo-graphs, onto:explicit for statements somebody actually asserted and onto:implicit for statements the reasoner produced. Neither is a real named graph. Both are queryable:
PREFIX archimate: <https://purl.org/archimate#>PREFIX onto: <http://www.ontotext.com/>PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>SELECT ?origin (COUNT(*) AS ?typeStatements) WHERE { ?element ?type . ?type rdfs:subClassOf* archimate:BusinessLayer . BIND(IF(EXISTS { GRAPH onto:explicit { ?element ?type } }, "asserted", "inferred") AS ?origin)}GROUP BY ?origin
Against ArchiSurance that returns 63 asserted and 63 inferred. Every business-layer element carries the type somebody gave it in the exchange file, plus a second type statement the reasoner derived by walking the class hierarchy. The inventory query in the next section returns those same 63 elements, and it finds them through the inferred half.

Learn this before you debug anything. A query that returns results you did not expect is often reading inferred statements, and a query that returns nothing is often missing them because the ruleset does not produce what you assumed. Being able to ask which half you are looking at turns both cases from guesswork into a query.
Patterns: From Inventory to Insight
Let me walk through a progression of query patterns, from the simplest inventory question to querying what the derivation engine produced. All of these run against a graph loaded with the ontology and a model, with derivation rules already fired.
Pattern 1 – Inventory: What Is in This Layer?
The simplest useful question. List every element in the business layer with its type and name:
PREFIX archimate: <https://purl.org/archimate#>PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>SELECT ?element ?type ?nameWHERE { ?element rdf:type ?type ; archimate:name ?name . ?type rdfs:subClassOf* archimate:BusinessLayer . ?type rdfs:subClassOf+ archimate:Element .}ORDER BY ?type ?name
The property path rdfs:subClassOf* archimate:BusinessLayer walks the class hierarchy: take the element’s type, follow subClassOf zero or more times, and ask whether BusinessLayer is anywhere on that path. A BusinessActor, a BusinessProcess, a BusinessService; they all resolve. The query never names a concrete type. It names one abstract class and lets OWL’s classification do the rest. The SHACL shapes from post 5 rely on the same mechanism; it is now available for querying instead of just validation.

Against ArchiSurance, this returns 63 business layer elements: 14 BusinessFunction, 13 BusinessActor, 11 BusinessProcess, 10 BusinessObject, 5 BusinessInterface, 3 BusinessCollaboration, 3 BusinessEvent, 2 BusinessService, 1 BusinessRole, 1 Product. Change BusinessLayer to ApplicationLayer or TechnologyLayer and the same query slices a different layer with no rewrite.
Counts are one way to read a layer. The Workbench has another. Visual Graph takes any element and expands its neighbourhood, which is the closest this stack comes to the thing an architect actually does when handed a diagram.
Pattern 2 — Relationship Traversal: What Serves This Capability?
A step up. Find every application component that serves a named business process:
PREFIX archimate: <https://purl.org/archimate#>PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>SELECT ?appName ?targetNameWHERE { ?app rdf:type/rdfs:subClassOf* archimate:ApplicationComponent . ?app archimate:serving ?target . ?app archimate:name ?appName . ?target archimate:name ?targetName . FILTER(STR(?targetName) = "Accept Policy")}
Against ArchiSurance, this returns Home & Away Policy Administration, the one application component that serves the Accept Policy business process. On a model with derivation rules fired, this catches both explicit serving relationships and derived ones, because the derivation engine materializes them as first-class triples. The query does not need to know whether the relationship was drawn or inferred. It is in the graph. It is queryable.

A note on language tags: ArchiMate’s exchange format carries xml:lang attributes on names, and the adapter preserves them as RDF language-tagged literals ("Accept Policy"@en, not "Accept Policy"). As covered in post 5, this is the correct RDF representation. In SPARQL, a plain string does not match a language-tagged literal, which is why the query uses FILTER(STR(?targetName) = "Accept Policy"). The STR() function strips the tag for comparison. The alternative is to match the tagged form directly with ?target archimate:name "Accept Policy"@en. Either works; STR() is more portable across models that may or may not carry language tags.
Pattern 3 — Metadata-Aware: Querying Relationship Attributes
ArchiMate relationships carry attributes. An access relationship has an access type (read, write, readwrite). An influence relationship has a sign (+, -, 0) and a strength. These are encoded as RDF-Star metadata on quoted triples, as covered in post 2.
Query the confidence level and provenance of any relationship, the metadata the derivation engine attaches:
PREFIX archimate: <https://purl.org/archimate#>SELECT ?sourceName ?rel ?targetName ?confidence ?identifierWHERE { ?source ?rel ?target . << ?source ?rel ?target >> archimate:confidence ?confidence . OPTIONAL { ?source archimate:name ?sourceName } OPTIONAL { ?target archimate:name ?targetName } OPTIONAL { << ?source ?rel ?target >> archimate:identifier ?identifier . }}ORDER BY ?confidenceLIMIT 20
The << ?source ?rel ?target >> syntax is RDF-Star: it quotes the triple and lets you query its annotations as if they were properties of the relationship itself. This is the first-class relationship argument from post 2, made queryable. The same pattern works for accessType, influenceModifier, isDirected and identifier — which, as it turns out, is the complete set of attributes the Exchange Format defines on a relationship. Against ArchiSurance with the valid derivation rules run to a fixpoint, dropping the LIMIT returns all 2,197 derived relationships, each tagged with a confidence of archimate:ValidDerivation and a derivationRule naming the rule that produced it.

The identifier column is empty on every row, and that is the interesting part rather than a fault in the query. ArchiSurance carries 600 archimate:identifier annotations and 2,197 archimate:confidence annotations, and not one quoted triple carries both. The two sets are disjoint, exactly.
They are disjoint because they answer different questions. An identifier is what the Exchange Format gives a relationship somebody drew: it is that relationship’s name in the source file, and the reason it survives a round trip. A derived relationship was never drawn, so there is nothing to carry over and the engine does not invent one. What it gets instead is a confidence and a rule.
So the pair works as a test. An annotation of identifier means a human put this edge here; an annotation of confidence means the engine concluded it. Nothing in the triple itself tells you which — Home & Away Policy Administration serving Accept Policy looks the same either way — and the distinction is not recoverable from the graph any other way once the two are mixed. That is a better argument for putting metadata on the quoted triple than the access types I reached for first, and it has the advantage of surviving the trip into RDF.
For models that carry access-type metadata, the same quoted-triple pattern can show accessType attribute:
PREFIX archimate: <https://purl.org/archimate#>SELECT ?sourceName ?targetName ?accessTypeWHERE { ?source archimate:access ?target . << ?source archimate:access ?target >> archimate:accessType ?accessType . ?source archimate:name ?sourceName . ?target archimate:name ?targetName . FILTER(?accessType = "write")}
| source | target | accessType |
|---|---|---|
Policy Data Management | Claim Data | write |
Policy Data Management | Policy Data | write |
Customer Data Access | Customer Data | write |
Pattern 4 — Derived Relationships: What Did the Engine Find?
Query every relationship the derivation engine produced, with its confidence level and the rule that generated it:
PREFIX archimate: <https://purl.org/archimate#>SELECT ?sourceName ?rel ?targetName ?confidence ?ruleWHERE { ?source ?rel ?target . << ?source ?rel ?target >> archimate:confidence ?confidence ; archimate:derivationRule ?rule . OPTIONAL { ?source archimate:name ?sourceName } OPTIONAL { ?target archimate:name ?targetName } FILTER(?confidence IN (archimate:ValidDerivation, archimate:PotentialDerivation))}ORDER BY ?confidence ?rule
This is the diagnostic query I run most often. It surfaces every fact in the graph that no one explicitly drew. The confidence column tells you whether the derivation is architecturally valid (DR2 through DR8) or merely potential (PDR1 through PDR12). The rule column tells you which derivation rule produced it. If a derived relationship surprises you, this query explains why it exists and how strong the basis is.

Both of those are IRIs rather than strings, and the distinction costs an afternoon if you miss it. archimate:confidence takes archimate:ValidDerivation or archimate:PotentialDerivation, not "valid" or "potential", and a FILTER written against the string form matches nothing and reports nothing. The rule is an IRI too, archimate:derivationRule deriv:DR3_StructuralDependency, so there is no need to recover it by slicing the human-readable description.
Against ArchiSurance, the derivation engine produced 2,197 relationships from 600 asserted ones, a little over three and a half times the model’s own edge count. The breakdown by rule: DR2 (structural chain) produced 835, DR4 (opposing structural plus dependency) 400, DR6 (structural plus flow opposite) 358, DR3 (structural plus dependency) 343, DR5 (structural plus dynamic) 242, and DR8 (triggering transitivity) 19. Every one is tagged, traceable, and queryable.
The two names are matched with OPTIONAL rather than as plain patterns, because three of these edges end at one of ArchiSurance’s four unnamed Junctions, and a plain match is an inner join that would drop them without saying so.
Three rows in twenty-two hundred hardly matters. The habit does. A name is presentation; archimate:identifier and the resource IRI are identity, and a Junction is a connector that never needed a label in the first place. Every time in this post that a query has joined on a name, it has been quietly deciding that unnamed things do not exist — and the way you find out is that two counts of the same thing disagree by three.
Two rules contributed nothing, and the reasons are different. DR7 (triggering plus structural) has 292 candidate pairs in this model and not one of them survives ArchiMate’s Appendix B.5 relationship table, so it derives nothing here. Junction transparency is the more interesting case: its edges were derived, just not by it. Every rule ends with FILTER NOT EXISTS { ?a ?r ?c } so that it does not re-derive what is already there, which means the first rule to reach an edge is the one that gets to claim it. DR2 got to ten of the junction pass-throughs first, DR3 to two, DR8 to one.
Which undercuts the table above: a per-rule breakdown is a property of the order you ran the rules in, not a property of the model. The total is stable. The attribution is not.
What the Graph Answers Well
The obvious thing to build on all of this is impact analysis: start at a failed device, follow every relationship outward, collect what breaks. So I built it. It produced a large number and I was pleased with it. Then I ran the same query from five unrelated starting points — a device, two application components, a business actor, a business process — and every one of them returned the same 228 elements, two-thirds of the model, reached in two hops. This is not so much a query or derivation issue but how the dependencies were asserted in the model.
So instead of impact analysis, I wrote three questions the ArchiSurance graph can answer in a way I would put in front of an architect. All of them share a shape: bounded, no transitive traversal, and asked against the relationships somebody actually drew.
What Realizes Our Capabilities?
A capability model is a claim about what the organization can do. The obvious follow-up is what actually does it. Ask which elements realize each capability, and group the answer by the type of the realizing element:
PREFIX archimate: <https://purl.org/archimate#>PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>SELECT (STRAFTER(STR(?realizerType), "#") AS ?type) (COUNT(DISTINCT ?realizer) AS ?realizers) (COUNT(DISTINCT ?capability) AS ?capabilitiesCovered)WHERE { GRAPH <https://purl.org/archimate/model/archisurance> { ?capability archimate:Capability . ?realizer archimate:realization ?capability . ?realizer ?realizerType . } # keep the most specific type only FILTER NOT EXISTS { ?subType rdfs:subClassOf ?realizerType . ?realizer ?subType . }}GROUP BY ?realizerTypeORDER BY DESC(?capabilitiesCovered)
Three rows come back, and the shape of them is the finding:
| realizing type | elements | capabilities covered |
|---|---|---|
Plateau | 4 | 22 |
BusinessFunction | 5 | 5 |
Junction | 2 | 2 |
Plateau is an implementation and migration element. It says “at this milestone, we will have this capability.” It does not say anything about what performs the work. Strip the extended layers back out and ask how many capabilities are realized by something in the core — business, application, or technology — and the answer is five, all of them by a BusinessFunction.
Forty-six capabilities. Five of them connected to anything that does work.
Which Capabilities Are Realized by Nothing?
The complement of that question is the more useful one, and it is a FILTER NOT EXISTS away:
PREFIX archimate: <https://purl.org/archimate#>SELECT ?capabilityWHERE { GRAPH <https://purl.org/archimate/model/archisurance> { ?c archimate:Capability ; archimate:name ?capability . } FILTER NOT EXISTS { GRAPH <https://purl.org/archimate/model/archisurance> { ?realizer archimate:realization ?c . } }}ORDER BY ?capability
Twenty-four of the forty-six come back with nothing realizing them at all. The full split: 5 realized from the core, 17 realized only by a Plateau, 24 realized by nothing.
And the names are the interesting part, because they are not obscure corners of the model. Risk & Compliance Management. Sales Execution. Product Definition. HR Management. Strategic Management. Enterprise Architecture, which I enjoyed. These are the capabilities an insurer would put on the first slide, and in the model they are floating: named, decomposed, and attached to nothing that performs them.
This is not a defect in ArchiSurance. It is what capability models look like almost everywhere, and it is exactly the problem the research literature on capability boundaries is trying to solve. It is also invisible on a diagram: a capability map with forty-six neat boxes looks complete, and the thing that makes it incomplete is an absence, which no diagram can show you. A query can.
How Many Capabilities Do We Actually Have?
Here is the one I did not expect to be able to ask. Take each capability, collect the set of elements that realize it, and use that set as a fingerprint. Then group the capabilities by fingerprint. Any two capabilities that come back in the same group are, as far as the model is concerned, the same thing.
PREFIX archimate: <https://purl.org/archimate#>SELECT ?signature (COUNT(DISTINCT ?cap) AS ?capabilities) (GROUP_CONCAT(DISTINCT ?cap; separator=" | ") AS ?names)WHERE { { SELECT ?cap (GROUP_CONCAT(DISTINCT ?midName; separator=",") AS ?signature) WHERE { GRAPH <https://purl.org/archimate/model/archisurance> { ?c archimate:Capability ; archimate:name ?cap . } OPTIONAL { ?mid archimate:realization ?c . ?mid archimate:name ?midName . } } GROUP BY ?cap }}GROUP BY ?signatureHAVING (COUNT(DISTINCT ?cap) > 1)ORDER BY DESC(?capabilities)
The inner SELECT builds one string per capability out of everything that realizes it; the outer one groups on that string. It is an ordinary aggregate query, it runs in a second, and the OPTIONAL is there so that capabilities realized by nothing get an empty fingerprint rather than dropping out.
ArchiSurance names forty-six capabilities. The query returns ten distinct fingerprints, and exactly two capabilities have one to themselves. The largest group holds twelve:
| capabilities sharing one fingerprint | realizers |
|---|---|
| Business Management, Business Support, Enterprise Architecture, Facility Management, Fiscal & Accounting Management, HR Management, Office Management, Organizational Development, Performance Management, Process Management, Risk & Compliance Management, Strategic Management | 5 |
| Claim Administration, Claim Settlement, Contract Administration, Contract Lifecycle Management, Data Acquisition, Data Analysis, Data-Driven Insurance | 14 |
| the seven Customer capabilities | 11 |
| the five Money capabilities | 8 |
| the four Investment capabilities | 9 |
| Sales, Product and Marketing, three each | 8-10 |
Risk & Compliance Management, HR Management, Strategic Management and Enterprise Architecture are not the same capability under any reading. The model cannot tell them apart.
The cause is mechanical, and understanding it beats deploring it. Capabilities decompose: eleven of the forty-six compose or aggregate children. Realization is the weakest structural relationship, so a chain of realization-then-composition derives realization, and whatever realizes a parent is therefore derived to realize every child. Each fingerprint group turns out to be exactly one branch of the decomposition tree. The graph discriminates capabilities down to the branch and is blind inside it.
Ask the same model which value stream each branch serves and the second half of the picture arrives. Four of the five value streams are served by capabilities, and four of the eleven branches serve none at all: Asset Management, Business Management, Business Support, Money Management. The twelve-capability blob is precisely two of those four. Nothing distinguishes those capabilities from each other and nothing connects them to value, which are not two separate findings.
That count needs care, and getting it wrong is instructive. My first version of the query asked only for ?capability archimate:serving ?valueStream and reported six orphaned branches. Two of them were not orphaned. Sales and Distribution and Marketing both serve Market and Sell Products, but they do it together, through a Junction:
Sales And Distribution --serving-->┐ ├─ Junction (10) --serving--> Market and Sell ProductsMarketing --serving-->┘
An AND junction, saying that the value stream needs both. A query that looks for a direct edge steps straight over it and reports an absence that is not there — which is the failure mode I warned about two sections ago, committed by me, in the query I was using to accuse someone else’s model of being incomplete. The junction is the connector that needed its own derivation rule back in post 3, and this is why: it is a real hop in the model that carries meaning, and every query that traverses relationships has to decide what to do about it.
Meanwhile Data-Driven Insurance serves two value streams while also sitting as a child of Policy and Claim Management, which is the kind of thing you would want to have noticed before publishing a capability map.
None of this is a defect in ArchiSurance specifically. Where one capability ends and the next begins is a genuinely hard modeling problem, and there is an active research literature on demarcating capability boundaries that exists because it is hard. What the graph contributes is not a solution. It is a measurement: forty-six names carrying ten capabilities’ worth of distinguishing information, computed in one query against a model nobody prepared for the question. A capability map is the one artifact that cannot show you this, because a capability map’s whole job is to draw forty-six boxes.
Where Is Everything Running?
Concentration is the other thing a graph is good at and a diagram is bad at. Count what each device carries:
PREFIX archimate: <https://purl.org/archimate#>SELECT ?device (COUNT(DISTINCT ?component) AS ?components)WHERE { GRAPH <https://purl.org/archimate/model/archisurance> { ?d archimate:Device ; archimate:name ?device . { ?d archimate:realization ?component } UNION { ?d archimate:assignment ?artifact . ?artifact archimate:realization ?component } ?component archimate:ApplicationComponent . }}GROUP BY ?deviceORDER BY DESC(?components)
ArchiSurance General-Purpose Server Cluster carries four application components, FO General-Purpose Server three, and every other device exactly one. Change Device to ApplicationComponent and realization to composition and the same query finds ArchiSurance Back Office Suite holding five sub-components, which is the cluster in the picture earlier in this post.
Note the deployment pattern this query had to encode. ArchiSurance hosts by Device realization ApplicationComponent and by Device assignment Artifact; it does not use Node assignment ApplicationComponent. I wrote the obvious version of this query first, got zero rows, and assumed the model was empty rather than that I had guessed the convention wrong. The graph will not save you from having to know how the model was built. It will only make the consequences of knowing legible.
What Touches the Data?
The last one is the question every data-protection conversation opens with, and it has an exact answer rather than a large one:
PREFIX archimate: <https://purl.org/archimate#>SELECT ?dataObject ?accessor (STRAFTER(STR(?accessorType), "#") AS ?type)WHERE { GRAPH <https://purl.org/archimate/model/archisurance> { ?d archimate:DataObject ; archimate:name ?dataObject . ?x archimate:access ?d . ?x archimate:name ?accessor ; ?accessorType . }}ORDER BY ?dataObject
| data object | accessed by | type |
|---|---|---|
Claim Data | Policy Data Management | ApplicationFunction |
Customer Data | Customer Data Access | ApplicationFunction |
Policy Data | Policy Data Management | ApplicationFunction |
Three data objects, two application functions, and one of those functions touching two of the three. That is a precise answer to “who reads customer data,” and the answer is not a business process. Nothing in the business layer accesses a DataObject in this model, which is correct ArchiMate — business behavior accesses BusinessObjects, and the DataObject realizes those:
| data object | realizes |
|---|---|
Claim Data | Claim |
Customer Data | Customer Record |
Policy Data | Insurance Policy |
Two hops, six rows, and a defensible answer to a regulator’s opening question. The version of this I wrote first fanned out through every relationship that touches customer data and returned ninety-seven elements including four buildings, three migration plateaus and six work packages. Ninety-seven is a bigger number. Six is an answer.
Graph Integrity: Trust Before Insight
The queries above are useful only if the graph is trustworthy. An absence query that misses a relationship is worse than no query at all: it reports a gap that is not there, and somebody is sent off to close it. This is why the validation layers from post 5 exist, and why the adapter pipeline includes integrity checks before export.
Three integrity queries that will help you keep your sanity in check:
Orphaned Metadata
RDF-Star metadata can become orphaned if a relationship triple is deleted but its annotations survive. This query catches it:
PREFIX archimate: <https://purl.org/archimate#>SELECT ?source ?predicate ?target ?metaPredicateWHERE { << ?source ?predicate ?target >> ?metaPredicate ?metaValue . FILTER( ?metaPredicate IN ( archimate:identifier, archimate:name ) ) FILTER NOT EXISTS { ?source ?predicate ?target . }}
Results here mean orphaned annotations: metadata about relationships that no longer exist. That is a data hygiene problem, not an ArchiMate problem, and it should be resolved before running analysis queries.
Duplicate Relationship Identifiers
The exchange format requires every relationship to have a unique identifier. In the graph, that means every quoted triple should carry at most one archimate:identifier:
PREFIX archimate: <https://purl.org/archimate#>SELECT ?source ?predicate ?target (COUNT(?identifier) AS ?identifierCount)WHERE { << ?source ?predicate ?target >> archimate:identifier ?identifier .}GROUP BY ?source ?predicate ?targetHAVING (COUNT(?identifier) > 1)
Any row here means two relationships in the source model collapsed onto the same quoted triple: same source, same type, same target, two identifiers. RDF deduplicates the edge, the exchange format does not, and the second identifier is the only evidence left that there were ever two.
Against ArchiSurance it returns ten. The model carries 600 relationship identifiers across only 590 distinct source-type-target triples, and the ten survivors are not scattered: eight of them are one Plateau, Target: CRM, Back Office and Data Warehouse Operational, aggregating the same seven application components twice over and realizing Data-Driven Insurance twice; the other two are ArchiSurance with a doubled flow to Customer and to Insurance Intermediary.
Nothing about that is visible on a diagram. Two identical arrows between the same pair of boxes render as one arrow, which is presumably how they survived into a published reference model. Nor is it visible in the graph, because RDF has already merged them; the count of identifiers is the only trace left. That is the whole case for putting relationship identity on the quoted triple rather than trusting the edge to carry it.
One caution on reading the result, which I got wrong first time. Group these by element name instead of by resource and the answer comes back fifteen, because ArchiSurance contains fourteen name collisions — Front Office exists as both a BusinessActor and a Grouping, and so do Back Office, Customer and eleven others. Grouping by name silently merges two legitimately distinct relationships and reports them as a duplicate. Names are labels. archimate:identifier and the resource IRI are the identity, and a query that confuses the two invents defects.
Dangling Endpoints
Relationships should connect elements that exist in the model. This query finds relationships where the source or target is not a recognized ArchiMate element:
PREFIX archimate: <https://purl.org/archimate#>PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>SELECT ?source ?predicate ?targetWHERE { ?source ?predicate ?target . # Match any concrete ArchiMate relationship property { ?predicate rdfs:subPropertyOf+ archimate:structuralRelationship } UNION { ?predicate rdfs:subPropertyOf+ archimate:dependencyRelationship } UNION { ?predicate rdfs:subPropertyOf+ archimate:dynamicRelationship } FILTER( NOT EXISTS { ?source rdf:type ?sourceType . ?sourceType rdfs:subClassOf+ archimate:Element . } || NOT EXISTS { ?target rdf:type ?targetType . ?targetType rdfs:subClassOf+ archimate:Element . } )}
These three checks are the graph equivalent of SELECT COUNT(*) FROM broken_foreign_keys. Unexciting but necessary. Run them before every analysis pass, or deploy them as SHACL shapes so the triple store runs them on every write transaction.
Reproducing This
Everything above came out of a repository built exactly this way. Two of the steps below fail silently when you get them wrong. The repository reports success, the queries run, and the answers are quietly incomplete.
Creating the repository
I run this on GraphDB. The Workbench lives at localhost:7200, and a new repository comes from Setup, then Repositories, then Create. Two of the fields on that screen decide whether the rest of this post works, and neither is obvious the first time.

Ruleset. Pick rdfsplus-optimized. Every query in this post that walks rdfs:subClassOf* to find elements by layer depends on the reasoner propagating rdf:type up the class hierarchy, and RDFS-Plus is the cheapest ruleset that does it. GraphDB will also compile a custom ruleset from a .pie file if you outgrow the built-in ones, which is the road the derivation work eventually went down, but nothing here needs it.
SHACL. Tick “Supports SHACL validation” while you are creating the repository. It is a creation-time decision, not a setting you flip afterwards, and if you skip it the validation files load without complaint and enforce nothing.
If you would rather script it, GraphDB takes a repository configuration as Turtle over the REST API, which is how I rebuild throwaway repositories to test rule changes without touching the working one:
rdfs: <http://www.w3.org/2000/01/rdf-schema#> . rep: <http://www.openrdf.org/config/repository#> . sr: <http://www.openrdf.org/config/repository/sail#> . sail: <http://www.openrdf.org/config/sail#> . graphdb: <http://www.ontotext.com/config/graphdb#> . shacl: <http://rdf4j.org/config/sail/shacl#> . rdf4j: <http://rdf4j.org/schema/rdf4j#> .[] rep:Repository ; rep:repositoryID "YOUR_REPO" ; rdfs:label "ArchiMate 3.2 ontology" ; rep:repositoryImpl [ rep:repositoryType "graphdb:SailRepository" ; sr:sailImpl [ # The SHACL sail wraps the GraphDB sail. Leave this out and you # get a repository that reports healthy and enforces nothing. sail:sailType "rdf4j:ShaclSail" ; shacl:shapesGraph rdf4j:SHACLShapeGraph ; shacl:validationEnabled "true" ; shacl:rdfsSubClassReasoning "true" ; sail:delegate [ sail:sailType "graphdb:Sail" ; graphdb:ruleset "rdfsplus-optimized" ; graphdb:enable-context-index "true" ] ] ] .
curl -X PUT -H 'Content-Type: text/turtle' \ --data-binary @repository-config.ttl \ http://localhost:7200/repositories/YOUR_REPO
That returns 204 and nothing else. The repository is there.
The sail:delegate nesting in that config is the whole ballgame, and it is the mistake I made first. A configuration that names graphdb:Sail directly, with no rdf4j:ShaclSail wrapped around it, is perfectly valid Turtle, returns the same 204, and produces a repository that looks identical in the Workbench. It simply has no SHACL in it. Everything you load into the shapes graph afterwards goes somewhere, reports success, and enforces nothing. This is the scripted version of forgetting to tick the box, and it is harder to spot, because there is no box you can go back and look at.
Loading the Ontology Into GraphDB
Before any of the queries below will run, you need the ontology in a repository. “Clone the repo and load the files” turns out not to be sufficient instruction, because there are two ways to end up with a repository that looks completely correct and is silently doing nothing. Both cost me an afternoon. Here is the order that works, and every one of these goes into its own named graph rather than into the default one. That is not fastidiousness: it is what lets you drop and reload one concern without rebuilding the repository, and what lets a query say which layer of the stack a statement came from.
ontology/archimate.ttl— the ontology itself.derivation/archimate_derivation_axioms.ttl— required. The permitted matrix, in both forms:archimate:permits<Relation>for what may exist at all,archimate:permitsDirect<Relation>for what a modeler may actually draw.derivation/archimate_derivation_strengths.ttl— required, and the one that fails quietly. It carries the relationship-strength ordering the weakest-link rules compare. Leave it out and those premises never bind, the rules derive nothing, and nothing anywhere reports an error.derivation/archimate_derivation_provenance.ttl— optional. Only needed if you want derived relationships to record where they came from.derivation/archimate_derivation_rules.ttl— the Appendix B derivation rules as SPARQLCONSTRUCT.validation/*.ttl— the SHACL layers, but read the next section before you load them anywhere.- Your model.
The layout I use, and the one every count in this post came out of:
| named graph | holds |
|---|---|
.../archimate/owl | ontology/archimate.ttl |
.../archimate/derivation | axioms, strengths, provenance, rules |
rdf4j:SHACLShapeGraph | the validation shapes |
.../model/archisurance | the model as loaded |
.../model/archisurance/derived | everything the rules produced |
I run this on RDFS-Plus (optimized). The ontology’s own queries walk rdfs:subClassOf* explicitly rather than leaning on the reasoner, so you are not obliged to turn on more than that.

Two ways to get it wrong
The shapes have to go in the shapes graph. If you switch validation on and load the SHACL files into the default graph, SHACL looks in rdf4j#SHACLShapeGraph, finds nothing, and enforces nothing. The repository reports validation as enabled. Every write succeeds. Every shape you wrote is sitting right there, fully queryable by SPARQL, doing absolutely nothing. This is the mirror image of the trap in What Enforcement Costs, where I put reference data into the shapes graph and no query could reach it. Same boundary, crossed in both directions, silent both times.
Don’t load derivation/conformance/. Those fixtures assert relationships between classes — they treat the metamodel itself as instance data, which is exactly what makes them usable as a conformance oracle, and exactly why they must never touch a repository holding a real model. They are test input, not reference data.
Check that it worked
Four queries, thirty seconds, and you know whether the repository is what you think it is. Run them one at a time. A SPARQL endpoint takes one query per request, and pasting the whole block returns a parse error rather than four answers.
PREFIX archimate: <https://purl.org/archimate#>PREFIX owl: <http://www.w3.org/2002/07/owl#># 1. The permitted matrix loaded. Expect 10,610 and 5,822.SELECT (COUNT(*) AS ?permitted) WHERE { ?s ?p ?o . FILTER(STRSTARTS(STR(?p), "https://purl.org/archimate#permits") && !CONTAINS(STR(?p), "permitsDirect"))}SELECT (COUNT(*) AS ?permittedDirect) WHERE { ?s ?p ?o . FILTER(STRSTARTS(STR(?p), "https://purl.org/archimate#permitsDirect"))}# 2. The strength ordering loaded. Expect 4. Zero means the weakest-link rules are dead.SELECT (COUNT(*) AS ?structural) WHERE { ?s archimate:structuralStrength ?o }# 3. The conformance fixtures are NOT loaded. This MUST return 0.SELECT (COUNT(*) AS ?fixtures) WHERE { ?s archimate:realization ?o . ?s owl:Class . ?o owl:Class .}# 4. The direct/derived distinction is live.# The first returns false, the second true -- and that gap is the whole point.ASK { archimate:ApplicationComponent archimate:permitsDirectRealization archimate:ApplicationProcess }ASK { archimate:ApplicationComponent archimate:permitsRealization archimate:ApplicationProcess }
Sit with that last pair. An ApplicationComponent realizing an ApplicationProcess is not something the specification lets you draw, and it is something a derivation may legitimately produce. Appendix B has always said both. Until post 7 my own ontology could only tell you the second one.
Loading ArchiSurance
Load the SHACL shapes first, if you want the import validated rather than merely accepted. They go in the shapes graph, never the default one. Same boundary as above, and the reason to be careful with that %23 a second time:
SG='http://rdf4j.org/schema/rdf4j%23SHACLShapeGraph'for f in core metamodel relationships; do curl -X POST -H 'Content-Type: text/turtle' \ --data-binary @validation/archimate_validation_$f.ttl \ "http://localhost:7200/repositories/YOUR_REPO/statements?context=%3C$SG%3E"done
Then confirm they are enforcing and not merely present, because those are different states and only one of them is visible. Write something a shape must refuse:
PREFIX archimate: <https://purl.org/archimate#>INSERT DATA { GRAPH <urn:probe> { <urn:probe:x> archimate:BusinessActor ; archimate:identifier "probe" ; archimate:name "probe" ; archimate:specialization <urn:probe:x> . }}
An element specializing itself is meaningless under any reading of the specification, so this must be rejected. A rejection comes back as a sh:ValidationReport with sh:conforms false and the message “An element cannot specialize itself.” If instead it succeeds, your shapes are loaded and inert. Either they went to the default graph, or the repository was not created on an rdf4j:ShaclSail with validation enabled.
There is a second check, and it reads backwards from what you would expect. Ask SPARQL how many shapes are in the shapes graph:
PREFIX sh: <http://www.w3.org/ns/shacl#>PREFIX rdf4j: <http://rdf4j.org/schema/rdf4j#>SELECT (COUNT(DISTINCT ?s) AS ?shapes) WHERE { GRAPH rdf4j:SHACLShapeGraph { ?s sh:NodeShape }}
A correctly loaded shapes graph returns zero. The ShaclSail holds its shapes outside the queryable data, so if SPARQL can see them, they are not being used as shapes. A non-zero answer means they landed in ordinary data and are decorative. I spent a while reading that number the wrong way round, on the reasonable assumption that finding my shapes meant they were there.
git clone https://github.com/AlbertoDMendoza/ArchiSurance-in-RDF.gitcurl -X POST \ -H 'Content-Type: application/x-turtlestar' \ --data-binary @ArchiSurance-in-RDF/ArchiSurance.ttl \ 'http://localhost:7200/repositories/YOUR_REPO/statements?context=%3Chttps://purl.org/archimate/model/archisurance%3E'
Three details in that one command, each of which will cost you an afternoon if you get it wrong. The content type is application/x-turtlestar, not text/turtle: relationship metadata is attached to quoted triples, and a plain Turtle parser rejects the file outright. The context parameter puts the model in its own named graph, which is what lets you tell asserted model content apart from the ontology it sits on, and several queries later in this post depend on that separation. And the # in that context IRI must be percent-encoded as %23, or curl treats it as a URL fragment, drops the parameter, and loads your model into the default graph where it mixes with the ontology. That one reports success.
It loads in about four seconds, 600 relationships across 361 elements. If you put the shapes in the shapes graph as described above, they are validated on the way in, and there are no violations. Stated plainly: the metamodel this series has been building accepts The Open Group’s own reference model without complaint. If you skipped that step the import is merely accepted, which looks identical and proves nothing.
Running the Derivation Rules
Loading archimate_derivation_rules.ttl does not execute it. The Appendix B rules are sh:SPARQLRule bodies, and RDF4J’s ShaclSail validates — it does not execute rules. Load the file and you get 21 rules sitting in the repository looking exactly like an inference engine, deriving nothing, complaining about nothing. Twenty of them have an executable body; DR1 does not, for the reason below. That is the same trap I walked into in the previous post, and I am restating it because every query in the rest of this post depends on derivation having actually run.
The rules are portable SPARQL, so you run them yourself. Take each rule’s sh:construct body, change CONSTRUCT to INSERT, and execute it as an update. DR3 is representative:
PREFIX archimate: <https://purl.org/archimate#>PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>INSERT { GRAPH <https://purl.org/archimate/model/archisurance/derived> { ?a ?depRel ?c . <<?a ?depRel ?c>> archimate:confidence archimate:ValidDerivation ; archimate:derivationRule deriv:DR3_StructuralDependency ; archimate:description "Derived via DR3: structural + dependency" ; archimate:derivedFrom <<?a ?structRel ?b>>, <<?b ?depRel ?c>> . }}WHERE { # ... the WHERE clause exactly as it appears in the rule's sh:construct body}
Two things there are easy to get wrong by paraphrasing. derivedFrom cites the two source edges as quoted triples, not as variables bound to relationship resources; the whole point of the RDF-Star encoding is that a relationship is the triple. And the GRAPH clause is mine rather than the rule’s. The sh:construct bodies do not name a graph, so a bare INSERT drops every derived edge into the default graph, mixed in with whatever the reasoner has been doing. Naming a graph costs one line and buys you a CLEAR GRAPH when you want to re-derive from scratch, instead of rebuilding the repository the way I did four times writing this.
Three things decide whether this works.
- Run the valid rules only — DR2 through DR8, plus
JunctionTransparency. Not the PDR set: the specification says a potential derivation “might be relevant but may also be wrong,” so asserting one asserts something the specification does not sanction. Not the utility rules either. On ArchiSurance,ComplexChainDerivationalone produces more than all eight valid rules combined, and none of it is Appendix B. - DR1 has no body, on purpose.
archimate:specializationis declaredowl:TransitivePropertyin the ontology, so any OWL-aware store already derives it. ACONSTRUCTthere would be a decorative rule that looks live and is not. - One pass is not the answer. Derived relationships are premises for other rules, so you run the set repeatedly until the triple count stops changing. On ArchiSurance that is four passes and just under two minutes. Do not guess at the number: the last pass is the one that tells you it was the last pass.
What’s Next? Federation…
The third problem from post 1: the system should match how the organization actually works. Distributed. Federated. Owned in parts. Post 1 gave that section the title “The Point of it All,” and I said this post would cover it. It is not covered here, and I want to be direct about why rather than let it quietly not appear.
Next post will be about Federation, properly: two domain graphs, a shared identifier, and a query that crosses them. Then populating the graph, automated architecture extraction from source code and infrastructure.
RDF was designed for this. Every resource has a URI, URIs are global, and two graphs that use the same URI for the same element are already linked, by construction, with no pipeline, no ETL job, and no shared database. Three teams publishing three ArchiMate models that all reference https://purl.org/archimate#ApplicationComponent and agree on the URI of one database server have a federated model whether or not anyone planned one.
Now, the hard half is not technical. If the reservation system calls its database myModel:ProdDB and the billing system calls the same machine billing:MainDatabase, the merge links nothing. URI alignment is a governance decision wearing a technical costume, and it is the same problem the Semantic Web faced at internet scale and never fully solved. What Nicolas Figay calls “The Inhabiting Babel” problem. My bet here is in not only the Enterprise as scope but also in using ArchiMate as the coordinate plane. I’m not asking the world to agree on identifiers and semantics, I’m asking three teams to use the same name for the same server. That is tractable, and it deserves more than the four paragraphs I would give it here.
So it gets its own post: two real domain graphs, a shared identifier, a SERVICE query that crosses them, and an honest account of what the naming convention costs to establish. A section would have been a description. A post can be a demonstration, and this series has been better when it demonstrates.
The ontology is independent and is not an official publication of The Open Group. ArchiMate is a registered trademark of The Open Group.






Leave a Reply