Model Validation: Catching Broken Architecture Before It Ships
A model can be broken in ways nobody notices until the moment it matters. A relationship whose target element was deleted three months ago. A serving relationship drawn between two things that, under the metamodel you claim to follow, cannot serve each other. Forty applications flagged Tier-1 and nine of them with no owner recorded anywhere. None of these show up on a canvas as anything other than a perfectly ordinary-looking diagram. That is precisely why they survive.
Software development worked out an answer to this class of problem a long time ago, and the answer was never "look harder." Nobody merges a change because it looked fine in the editor. It gets compiled, type-checked, tested, and run past a linter, and each of those steps catches a different category of wrong. A compiler will not tell you your business logic is backwards. A test suite will not tell you your variable names are inconsistent. You need all of them, and you need them in that order, because a type error makes the test results meaningless.
Architecture models rarely get the same treatment. They get a review meeting — once a quarter, if the organisation is disciplined — where three or four people scroll through whatever diagrams happen to be open and comment on the ones that catch their eye. Everything not opened is implicitly certified as fine. Everything opened is checked against whatever the reviewers remember of the standards. It is an honest effort applied to an impossible task, and the result is a repository that is mostly right, unevenly, in ways nobody can characterise.
Model validation is the argument that an architecture model deserves the same layered, automatic treatment that source code gets before release. Not one big "is this model good" check, because no such check exists, but four distinct layers, each answering a different question, each with a different failure mode, running on every change rather than every quarter.
Four layers, four different questions
The four layers are worth stating plainly before going into any of them, because the most common mistake in this area is collapsing them into one. People build a single "model checker" that mixes a dangling-reference error together with a naming-convention warning together with a governance violation, dumps 400 findings into a report, and then watches the report get ignored. The findings were all real. The problem was that they answered four incompatible questions and demanded one reaction.
- Structure. Is this a well-formed model at all — does every reference resolve, does every object have the shape the schema requires?
- Metamodel. Is this combination legal in the modelling language you have chosen to use?
- Rules. Does this comply with what your organisation actually decided, expressed as deterministic pass/fail checks?
- Lint. Is this good work, even where it is well-formed, legal, and compliant?
Structure and metamodel are universal — they are properties of the model and the profile, not of your organisation, and they are either satisfied or they are not. Rules are yours: nobody else's governance board made your decisions. Lint is advisory by design and should never block anything. Four layers, four severities, four different people who care.
Layer one: is this a well-formed model at all?
Structural validation is the least glamorous layer and the one that saves you the most pain, because everything above it depends on the model being internally coherent. It asks questions with no interpretation attached. Does every relationship have a source and a target that both resolve to elements that exist? Does every element have a type? Does every view reference only elements that are actually in the model? Are all identifiers unique? Is anything nested inside its own descendant?
These faults sound like they should be impossible in a tool that keeps a canonical model with stable identity — and in normal interactive use, most of them are. You cannot easily drag a relationship to nowhere. But models do not only get edited interactively. They get imported from Archi and from Sparx EA, where the source export may itself be partial or may reference objects living in a package that was not included. They get merged from two branches where one side deleted an element and the other side attached a new relationship to it — a conflict that resolves cleanly at the file level and produces a dangling endpoint at the model level. They get modified by scripts, by bulk operations, and by AI-proposed changes. Every one of those paths can produce a model that renders fine and is structurally unsound.
A structural failure report should be boring and precise, naming the object and the exact defect:
STRUCTURE: 4 errors
E-STRUCT-001 rel_a91f target 'el_44c2' does not exist
(relationship 'Serving', source 'Billing Portal')
E-STRUCT-001 rel_b03d source 'el_44c2' does not exist
(relationship 'Access', target 'Invoice Store')
E-STRUCT-014 el_9d20 element has no type
(name 'Data Warehouse (new)', imported 2026-04-11)
E-STRUCT-022 view_07 view references 'el_44c2', not present in model
(view 'Finance Application Landscape')
Notice that three of the four errors point at the same underlying event: element el_44c2 was removed while three things still referenced it. That is the normal shape of structural damage. One deletion, several symptoms, none of them visible unless something goes looking. A human reviewing the Finance Application Landscape view would see a diagram with one fewer box on it than they remembered, assume it was intentional, and move on.
The reason to run this layer first and treat it as blocking is not tidiness. It is that every other layer walks the same graph. A rule that traverses relationships to find cross-domain access will hit rel_b03d, fail to resolve its source, and either crash or silently skip it — and a silently skipped relationship becomes a rule that reports PASS on a model containing exactly the violation it was written to catch. A false pass is considerably worse than an error, because it is indistinguishable from good news.
Layer two: is this legal in the language you chose?
A structurally perfect model can still be nonsense in the modelling language you have declared. Metamodel validation checks the model against the profile in use — Generic, ArchiMate, C4, or whatever else the tool supports — and answers a narrower question: given these two element types, is a relationship of this type between them permitted at all?
ArchiMate is the obvious example because it is the profile with the most opinions. It defines which relationships may connect which concepts, and those constraints exist for reasons: an Access relationship expresses behaviour touching passive structure, so an Application Component accessing a Business Actor is not a stricter-than-necessary rule being pedantic, it is a statement that does not mean anything. A Data Object cannot serve an Application Service, because serving is something active or behavioural elements do and a Data Object is neither. A Business Process cannot be assigned to an Application Component; assignment runs the other way. Every one of these is easy to draw. None of them is easy to spot by eye six months later in a view with sixty elements on it.
C4 has fewer relationship constraints and stronger containment ones. A Component belongs inside a Container, a Container inside a System; a Person is never inside anything. A model where somebody has nested a Container inside a Component because it made the diagram lay out more nicely is a model whose C4 level boundaries have quietly stopped meaning anything, which matters the moment anyone tries to generate a context-level view from it.
There is an honest tradeoff to name here. Metamodel validation is only as strict as the profile you picked, and the Generic profile is deliberately permissive — it will let almost any relationship connect almost any pair of elements, because its job is to get out of the way while you are still working out what you are modelling. That is a legitimate choice for early exploratory work and a bad choice for a repository three departments depend on. If you are on Generic, layer two is close to a no-op, and you should know that rather than assume it is quietly protecting you. Switching a maturing model onto ArchiMate or C4 is the moment layer two starts earning its place, and it is also the moment you find out how many convenient-but-meaningless relationships accumulated while nothing was checking.
Layer three: does this comply with what you decided?
Layers one and two are about correctness in a sense that has nothing to do with your organisation. Layer three is entirely about your organisation. Architecture rules encode decisions — the ones made in governance forums, written into standards documents, and then, in most places, never checked again except by whoever happens to remember them during a review.
A good rule is deterministic and binary. It queries the model's elements, properties and relationships, and returns a definite pass or a definite fail with a list of offending objects. No judgement, no scoring, no "consider whether." If a check cannot be expressed that way, it is not a rule, it is a guideline, and it belongs in the linting layer or in a human's head. Typical examples that do work as rules:
- Every application with classification Tier-1 must have a non-empty owner property.
- No application in one business domain may hold a direct read or write relationship to a data store owned by another domain — integration goes through a service, not through someone else's database.
- Every internet-facing application must reach internal services through the approved API gateway rather than directly.
- No application with lifecycle status Production may depend on a component with lifecycle status Deprecated.
- Every application must be linked to at least one business capability it supports.
What makes this layer valuable is not the individual checks, which are mostly unsurprising. It is that the decisions stop living exclusively in a slide deck. "No direct cross-domain database access" is the sort of principle that everybody agrees with in the room and that gets violated four times in the next eighteen months, each time for a locally excellent reason, each time invisibly. Written as a rule that runs on every change, it becomes something that has to be argued with explicitly rather than bypassed silently. That is a meaningful shift in how governance works: the default outcome of not paying attention flips from "the violation lands" to "the violation is named."
Rules also need owners and a review cadence, exactly like the policies they encode. A rule that no longer reflects a decision anyone stands behind is worse than no rule, because it trains people to click past failures. The rules live with the model, get versioned with the model, and get changed through the same review process as anything else in it — which at least means the question "why do we still enforce this?" has a place to be asked.
Layer four: is this good work?
The fourth layer catches things that are not wrong. A model can be structurally sound, legal under ArchiMate, fully compliant with every governance rule, and still be poor quality in ways that make it hard to use. Linting flags those smells without blocking anything.
- Orphaned elements. An element with no relationships and no presence in any view. Sometimes a genuine placeholder for work in progress; more often something imported years ago and never connected to anything, quietly inflating every count anyone reports off the repository.
- Missing descriptions. An element named "CDS" with no documentation is a box that means something to exactly one person. The lint check cannot know whether the description is good — only that there isn't one.
- Probable duplicates. "Payment API" and "Payments API" as two separate elements, each with their own relationships, each half the picture. This is a heuristic and will produce false positives, which is exactly why it belongs at the advisory layer rather than the blocking one.
- Inconsistent naming. Half the applications named "Customer Portal" and half named "APP-CUSTPORTAL", because two teams imported from two sources under two conventions.
- Circular dependencies. A depends on B depends on C depends on A. Sometimes legitimate; usually a sign that somebody modelled a bidirectional integration as two directed dependencies without thinking about what that implies for change sequencing.
- Unresolved soft references. A description or property that names an element which no longer exists under that name — not a broken pointer in the structural sense, but a piece of prose that has gone stale.
The discipline required here is restraint. Every lint finding is a judgement call about what good looks like, and if you set them all to blocking you have not raised quality, you have taught your architects that validation is an obstacle to be disabled. Lint warns. It shows up in a summary, it trends over time, and it gives a team something to chip away at. It does not stop a change from being applied.
The four layers side by side
| Layer | What it catches | Failure example | Severity |
|---|---|---|---|
| Structure | Malformed model: unresolved references, missing types, duplicate identifiers, impossible nesting | Relationship rel_a91f points at an element deleted in a merge two weeks ago | Blocking |
| Metamodel | Combinations the chosen profile does not permit | An Access relationship from an Application Component to a Business Actor under ArchiMate | Blocking |
| Rules | Violations of decisions your organisation actually made | Tier-1 application with an empty owner property; a Claims app writing directly to a Finance data store | Blocking, with explicit waivers |
| Lint | Quality smells that are legal, compliant, and still bad | "Payment API" and "Payments API" as two elements; nineteen elements with no description | Advisory |
Why the order is not negotiable
Running these layers in the wrong order does not just waste time. It produces confidently wrong results, which is a considerably more expensive failure than producing none.
Take the cross-domain database rule from earlier. It works by walking outward from each application, resolving each relationship's target, checking the target's owning domain, and failing if the target is a data store in a different domain. Now feed it a model where three relationships have dangling targets. The resolution step returns nothing. A naive implementation throws and the whole run dies, which is annoying but at least visible. A defensive implementation skips the unresolvable relationship and carries on — and now the rule reports zero violations across a model that contains a genuine violation on one of the relationships it skipped. Everyone reads the green result and believes it.
The same failure mode applies one layer up. Metamodel validation on an element with no type cannot decide anything; there is no type to check the relationship against. And linting a structurally broken model generates noise rather than signal: every element whose relationships have dangling endpoints looks like an orphan, so the orphan count triples and the finding becomes worthless. You end up chasing forty phantom orphans that are really one broken merge.
Every change, not every quarter
The layered model is only half the argument. The other half is when the layers run — and the honest comparison is not "automatic validation versus nothing." Most organisations with a serious repository do have a model quality process. It is periodic, it is manual, and it is structurally worse than running the same checks continuously, for reasons that have nothing to do with how careful the reviewers are.
A periodic review samples. Nobody opens 600 elements; they open the ones they suspect. Continuous validation is exhaustive by construction, because a machine checking every object costs the same whether the model has 60 elements or 6,000.
A periodic review measures a state. Continuous validation measures a delta. This turns out to matter enormously, because the delta is small. A quarterly review of a mature model produces a wall of findings — hundreds of them, accumulated over months, attributable to nobody in particular, none individually urgent. That list gets triaged once, partially, and then becomes wallpaper. A per-change run produces one finding, attached to the change that caused it, in front of the person who has all the context loaded right now. That finding gets fixed in two minutes because fixing it is easier than filing it.
Attribution is the underrated part. When a violation is caught at the moment it is introduced, you do not have to reconstruct anything: you know which change, which author, which intent. When it is caught eleven weeks later, you are doing archaeology on a decision whose rationale existed only in a conversation. Half the time the reviewer concludes it was probably deliberate and leaves it, which is how repositories accumulate their permanent layer of unexplained oddity.
Mechanically, this is straightforward when the model has a deterministic, canonical serialization — the kind where the same model always produces byte-identical output, so a diff shows semantic change rather than incidental reordering. Mooodels serialises models that way specifically so they can live in version control, which means validation can run as an automated check on a pull request in exactly the same position a test suite occupies: on the change, before the merge, with the result attached to the proposal rather than delivered as a report afterwards. The architecture change and the code change go through structurally the same gate.
A worked example: an AI-proposed change through four gates
Validation earns its keep most visibly on changes that did not come from a human dragging boxes around. An AI assistant asked to "add a claims intake API that serves the claims portal and reads the policy data store" does not edit the model directly. It proposes a structured ModelPatch — a set of explicit operations — which then goes through the same four gates any other change would:
{
"operations": [
{ "op": "add", "kind": "element", "id": "el_7f2c",
"type": "ApplicationComponent", "name": "Claims Intake API",
"properties": { "domain": "Claims", "tier": "1" } },
{ "op": "connect", "kind": "relationship", "id": "rel_9a41",
"type": "Serving", "source": "el_7f2c", "target": "el_3b88" },
{ "op": "connect", "kind": "relationship", "id": "rel_9a42",
"type": "Access", "accessType": "ReadWrite",
"source": "el_7f2c", "target": "el_5d10" }
]
}
Gate one passes: every identifier referenced already exists or is created by the patch itself, every new object has a type, nothing is nested inside itself. Gate two passes as well — an Application Component serving another Application Component is legal ArchiMate, and an Application Component with an Access relationship to a Data Object is exactly what Access is for. So far the patch is well-formed and speaks correct ArchiMate. It is also, as it happens, in breach of two organisational rules:
STRUCTURE pass (3 operations, 0 errors)
METAMODEL pass (profile: ArchiMate 3.2)
RULES 2 failures
R-TIER1-OWNER FAIL el_7f2c
Tier-1 application has no 'owner' property
R-NO-XDOMAIN-DB FAIL rel_9a42
'Claims Intake API' (domain: Claims) has ReadWrite access to
'Policy Data Store' (domain: Underwriting) with no intermediary
LINT 2 warnings
W-NO-DESC el_7f2c element has no description
W-NAMING el_7f2c 'Claims Intake API' does not match the
domain naming pattern 'CLM-*'
This is the interesting case, because the AI did nothing wrong in any sense it could have known about. The request was reasonable, the patch is a faithful interpretation of it, and both structural and metamodel layers agree it is a legitimate model change. What it violated were two decisions specific to this organisation — an ownership requirement and a cross-domain integration standard — that exist nowhere in ArchiMate and could not have been inferred from the request. The rules layer is where local knowledge lives, and it is the reason an assistant proposing changes does not need to be trusted to know your standards: it needs only to be checked against them.
The reviewer now has a specific conversation to have rather than a vague unease. Should the intake API read the policy store directly, or should Underwriting expose a service? Who owns this thing? Those are architecture questions, surfaced at the moment they are cheap to answer, by a check that ran in the time it took to render the patch. Nothing is applied until a human approves it — and if the reviewer decides the direct access is genuinely justified, that decision gets recorded as an explicit waiver rather than as a rule quietly removed.

Severity, waivers, and the rule everyone ignores
Everything above describes a system that works. Here is what breaks it, all of which shows up within about a month of switching validation on for a repository that has never had it.
The first failure is severity inflation. It is tempting to make every check blocking, on the reasoning that if it was worth writing it is worth enforcing. What actually happens is that a naming-convention warning stops a legitimate urgent change, somebody works out how to bypass the check, and the bypass becomes the normal path. Lint must warn. Rules must block only where the organisation genuinely means to block. If a rule fires constantly and is waived every time, it is not a rule and pretending otherwise erodes the credibility of the rules that matter.
The second is the wall-of-violations problem. Turn four layers on against a repository built over six years without any of them, and you will get a report with a four-figure finding count. The reaction to that report is never "let us fix these"; it is "this tool is unusable." The workable approach is baselining: record the existing violations as a known, tracked backlog, and configure the gate so that new violations block while pre-existing ones only appear in a trend. That way the model stops getting worse immediately, which is the achievable goal, rather than demanding it become perfect first, which is not.
The third is waivers without expiry. Every real organisation has justified exceptions — the legacy integration that will be replaced next year, the emergency change agreed with the security team. Those need to be recordable, or the rule gets deleted. But a waiver with no owner and no expiry date is functionally the same as deleting the rule for that object, just less visible. A waiver should say who granted it, why, and when it lapses, and it should live in the model next to the thing it excuses, so that a year later somebody can ask whether the temporary exception is still temporary.
The fourth is rules nobody owns. Rules encode policy, policy changes, and a rule that has outlived the decision behind it produces failures that are correct according to the check and wrong according to everyone in the building. Treat the ruleset as a maintained artifact with review cycles, not as a configuration file someone wrote during onboarding.
The honest limit: valid is not true
All four layers together prove something narrower than people assume. They prove that the model is internally consistent, legal under its declared profile, and compliant with the rules you have written down. They prove nothing whatsoever about whether the model describes the systems you actually operate.
A model of an application that was decommissioned fourteen months ago can be flawless. It has a type, an owner, a description, sensible relationships, no rule violations, and no lint warnings. Every gate passes. It is also fiction. Conversely, a production database connection that nobody ever modelled generates no findings at all — validation can only examine what is present, and the most dangerous architectural facts are frequently the ones absent from the repository entirely. Validation has no opinion about absence, because absence looks exactly like a model that is simply smaller.
Validation tells you the model is coherent. It cannot tell you the model is correct. Those are different claims, and conflating them is how a repository becomes confidently, systematically wrong — clean, green, and describing an estate that no longer exists.
What actually closes that gap is a different discipline entirely: reconciliation against authoritative sources. Import the service catalogue, the CMDB, the cloud inventory, the deployment pipeline's list of running applications, and diff them against the model. That will find the decommissioned application and the undocumented database, because those sources are derived from reality rather than from someone's recollection of it. It is genuinely useful and it is not validation — it is comparison against an external ground truth, with all the messiness that implies, including that your CMDB is probably also wrong in places.
Beyond that, the only remaining mechanism is a human who knows the estate looking at the model and saying "this is not how it works." That does not scale and cannot be automated, so the sensible move is to make it cheap and to record its results. A last-verified-by and last-verified-on property on each element, plus a lint check that flags anything unverified for more than a year, is about as close as validation gets to reality — and it is worth being clear about what it measures, which is the age of a human's claim rather than the claim's accuracy. Still, an architect reviewing thirty stale elements is a far better use of an hour than an architect scrolling diagrams hoping something looks off.
What layered validation actually buys
The argument here is not that automated checks make an architecture model true. It is that they eliminate an entire class of problems that should never have consumed human attention in the first place. Nobody's judgement is required to notice a relationship pointing at a deleted element, or an Access relationship to a Business Actor, or a Tier-1 application with an empty owner field. Those are mechanical faults, and letting them survive until a quarterly review means the reviewer spends their limited attention on bookkeeping instead of on the questions only they can answer.
Run structure, metamodel, rules and lint on every change, in that order, with an honest "not evaluated" when a layer could not run, and what reaches a human reviewer is a model that is already known to be well-formed, legal, and compliant. The conversation that remains is the one worth having: is this how the organisation should be built, and does this picture still match the systems that exist? That is what Mooodels is built to leave you with — the checks handled deterministically, and the judgement handed back to the people who can exercise it.
A model that passes every gate is not a model you can trust blindly. It is a model whose remaining errors are all interesting ones.
See the model this article describes, working in a real editor.
Try the live demo