The SysML v2 Book
This book teaches you textual SysML v2 by building a model, not by reading the specification.
You will start with an empty file, grow it into a realistic system model, and learn each language feature as you need it. The running example throughout is a Smart Coffee Machine.
How to Read This Book
If you are new to SysML v2, start at Chapter 1 and read in order. Each chapter builds on the one before it, and the coffee machine model grows as you go.
If you already model in SysML v2, jump to the chapter you need. Chapters 3 to 10 cover the core language and Chapter 11 assembles it into one model. Chapters 12 to 14 cover specialization, metadata and views, and libraries; Chapters 15 and 16 cover working in projects and running what you have built. The ten appendices are reference material: syntax, migration from SysML v1, the Systems Modeling API, tooling notes, physics-aware simulation, diagnostic codes, relationship and element kinds, lexical rules, review checklists, and occurrences, time slices, and snapshots.
What You Will Build
By Chapter 11, you will have a complete system model with structure, behavior, requirements, and verification links – all connected into one coherent design.
About this book
The book teaches SysML v2 as the OMG specification defines it, and cites clauses where a claim is load-bearing so you can check it for yourself. Where something is a particular tool’s behaviour rather than the language’s, it says so – usually in a note like this one – so you can skip it and still learn the language.
This book is the language half of a documentation pair. Its companion, the sysml-rs documentation portal, documents the sysml-rs tool itself – installation, projects and dependencies, the CLI, execution, editors, and integrations. Tool-specific notes in these chapters link there rather than duplicating it.
Every code sample is checked against a real parser rather than written from memory.
This is a pre-1.0 draft. Chapters are still being verified against the specification, and some are thinner than others. If you find something wrong, please say so – corrections are the most useful contribution.
The prose is licensed CC-BY-4.0. The code samples are MIT OR Apache-2.0, so you can lift one into your own model without picking up an attribution obligation.
Why Textual SysML v2
SysML v2 is a language for describing systems – what they are made of, how they behave, and what they must do. You write it as plain text, the same way you write code.
Here is a complete SysML v2 model:
package CoffeeMachine {
part def Grinder;
part def Brewer;
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
}
}
Read it as: a CoffeeMachine contains a Grinder and a Brewer. It is not a
diagram, and it is not a database. It is text in a file, and that file is the
model.
Definition and usage: the idea everything else rests on
Look again at those seven lines. Grinder appears twice, and the two
appearances mean different things.
part def Grinder introduces a definition – the kind of thing a grinder
is. There is one of these no matter how many grinders exist.
part grinder : Grinder introduces a usage – one particular grinder, in
the context of this coffee machine, typed by that definition.
If you have written code, the instinct is to read this as class and instance. It
is close enough to get started, and Chapter 3
will sharpen it. What matters now is that the split is not a convention someone
adopted – it is built into the language. The specification gives definitions and
usages their own clause of the textual notation (SysML §8.2.2.6), and nearly
every construct you will meet comes in both forms: action def and action,
state def and state, requirement def and requirement.
Learn to ask “is this a kind, or a particular one?” and a large part of SysML v2 stops being a vocabulary problem.
Why text
When your model is a text file, you inherit the tooling you already have:
- Diff and review. A change shows up as a line-by-line diff. A reviewer can comment on the exact line where a requirement changed or a port was renamed.
- Version control. Branch, merge, and tag model versions the way you tag releases. The model’s history is the repository’s history.
- Automation. A pipeline can parse the model, check its constraints, and fail the build before anyone sits down to a review meeting.
None of that is available when the model lives in a proprietary binary, and none of it needs a special tool beyond the ones you already run.
What the specification actually says about notation
It would be convenient to claim text is the one true form of a SysML v2 model. That is not what the standard says, and the real answer is more useful.
SysML v2 defines two concrete notations: a textual notation (§8.2.2) and a graphical one (§8.2.3). Both are normative. A tool can demonstrate Textual Notation Conformance, Graphical Notation Conformance, or both – and a tool claiming the graphical variant must support the textual notation at least far enough to render text inside diagrams correctly (SysML §2).
Underneath both sits the abstract syntax: the metamodel of elements and relationships that a model really consists of. Notation is how you write it down. This is why diagrams and text are not rivals. They are two projections of one model, which is also why a diagram can be generated from text but a well-formed model cannot be recovered from a picture alone.
Two conformance levels are mandatory for every conformant tool: Abstract Syntax Conformance and Model Interchange Conformance. Notation support is optional (SysML §2). So a tool is allowed to read and write SysML v2 models without offering either notation – which tells you where the standard thinks the model lives.
Interchange is specified too: projects travel as a .kpar archive, and textual
files inside it use the .sysml extension. Those extensions are normative, not
conventions.
Where SysML v2 came from
SysML v1 was a profile of UML – a set of stereotypes layered onto a general-purpose software modeling language. SysML v2 is not. It is a metamodel that extends the Kernel Modeling Language (KerML), a domain-independent language for building semantically rich modeling languages (SysML §1).
Two consequences you will feel while reading this book:
- Names from KerML surface constantly.
Occurrence,Feature,Classifier,Specializationare KerML’s, not SysML’s. When a chapter cites a KerML clause, that is why. - The standard library is part of the language. Types like
Real,Stringand the ISQ units are defined in model libraries that ship with the specification (Clause 9), not baked into the grammar.
The standard also specifies a formal transformation from SysML v1.7 to v2, so migration is a defined activity rather than a rewrite from scratch. That is Appendix B.
What this book builds
Sixteen chapters that grow one model – a smart coffee machine – from an empty file into a complete specification:
- Chapters 2–5 build the structure: definitions and usages, packages, parts, ports, attributes.
- Chapters 6–8 add behaviour: connections and interfaces, actions with control flow, states with guarded transitions.
- Chapters 9–10 make it checkable: expressions, constraints, requirements and verification cases.
- Chapter 11 assembles the whole thing into one coherent model.
- Chapters 12–14 go deeper: specialization and redefinition, metadata and views, libraries.
- Chapters 15–16 are about working with a model: projects and workspaces, and what it means to run one.
Then ten appendices of reference material – syntax, migration, the API, tooling notes, physics-aware simulation, diagnostic codes, relationship kinds, lexical rules, review checklists, and occurrences, time slices, and snapshots.
How to read it
Start at Chapter 2 and work forward; each chapter builds on the one before, and the model assumes you have the earlier chapters. If you already model in SysML v2, jump to what you need and follow the back-references.
Where a claim about the language is load-bearing, the text cites the clause so you can check it. Where something is a particular tool’s behaviour rather than the language’s, it says so – usually in a note like the ones you will meet in Chapter 8 – so you can skip those and still learn SysML v2.
Blocks marked sysml are real model text, and every one of them is checked
against a parser before this book ships. Most are complete enough to drop into a
.sysml file as they stand; some are fragments of a larger model, and a few use
... to elide a part you have already seen. Blocks marked text are syntax
templates with <angle> placeholders – shapes to read, not code to run.
You do not need the tool to follow along, but if you want it, Appendix D explains how to get it.
Here is that same coffee machine as a diagram – the same model, the other notation:
Build Your First Model
In this chapter, you will build a coffee machine model from an empty file. By the end, you will have a typed, multi-definition model with attributes and imports – and you will understand the core concepts of SysML v2 through building, not through reading definitions.
Create a file called coffee_machine.sysml and follow along. After each step, you can run
sysml check coffee_machine.sysml to verify your model.
You do not need the tool to follow the chapter – reading the models is enough. If you do
want to run the commands, Appendix D explains how to get the
sysml binary; it is a build from source rather than a package install, so it is worth
setting up before you start rather than partway through.
The finished version of everything this book builds is already on disk, under
examples/coffee-machine/. Chapter 11 maps each file to the
chapter it belongs to. Peeking is allowed.
Step 1: An Empty Package
Every SysML v2 model lives inside a package. A package is a namespace – it groups related model elements and gives them a home.
Type this into your file:
package CoffeeMachineDomain {
}
That is a valid model. It does not describe anything yet, but it parses and checks cleanly. A package with nothing inside it is like an empty directory: a place waiting for content.
Run sysml check and confirm there are no errors.
Step 2: Your First Definition
Now add something inside the package. In SysML v2, a definition declares a reusable type – a blueprint for something that can exist in your system.
package CoffeeMachineDomain {
part def Grinder;
}
part def Grinder says: there is a kind of thing called a Grinder. It is a part definition – a structural component type. You have not placed a grinder anywhere yet. You have only said that grinders exist as a concept in your domain.
The keyword part def is how SysML v2 spells “define a component type.” You will see this pattern everywhere: <keyword> def <Name>.
Step 3: A Usage Inside a Definition
A definition by itself is just a type. To say that a coffee machine contains a grinder, you create a usage of that type inside another definition:
package CoffeeMachineDomain {
part def Grinder;
part def CoffeeMachine {
part grinder : Grinder;
}
}
part grinder : Grinder says: inside a CoffeeMachine, there is a part called grinder, and it is of type Grinder. This is a typed usage – the most fundamental pattern in SysML v2.
Notice the difference:
part def Grinder– defines a type (uppercase, no colon)part grinder : Grinder– uses that type in context (lowercase name, colon, type name)
This definition/usage split is how SysML v2 keeps models reusable. You define a type once, then use it wherever you need it.
Step 4: Adding More Definitions
A coffee machine has more than a grinder. Add a brewer and a water tank:
package CoffeeMachineDomain {
part def Grinder;
part def Brewer;
part def WaterTank;
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
}
}
Run sysml check. No errors. The model now says: a CoffeeMachine is composed of three subsystems, each with a named role.
Here is how this model looks in an interactive general view. Each definition appears as a labeled block with its stereotype — «part definition», «item definition», and so on. Pan or zoom to inspect it:
Notice that each definition is independent. Grinder does not know about Brewer. They only meet inside CoffeeMachine, where they each have a named usage. This separation is intentional – it keeps definitions reusable across different contexts.
Step 5: Attributes and Imports
Definitions without properties are just names. To make the model useful, add attributes – measurable or configurable properties of each component.
Attributes need types. SysML v2 provides built-in scalar types like Real, Integer, Boolean, and String in the ScalarValues library. To use them, you need an import:
package CoffeeMachineDomain {
import ScalarValues::*;
part def Grinder {
attribute grindSize : Real;
}
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
}
part def WaterTank {
attribute currentLevel : Real;
attribute maxCapacity : Real;
}
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
attribute serialNumber : String;
}
}
Several things happened:
import ScalarValues::*brings all scalar types into scope. The::*means “import everything from this package.” Without this line,RealandStringwould be unresolved names.- Each definition now has
attributemembers. An attribute is a property with a type:attribute grindSize : Realsays the grinder has a numeric grind size setting. CoffeeMachineitself has aserialNumberattribute. Definitions can have both part usages (components) and attribute usages (properties).
Run sysml check again. If you forget the import line, you will see errors like “unresolved name: Real.” That error tells you exactly what is missing – add the import and the errors disappear.
Step 6: Items – Things That Flow
Not everything in a system is a structural component. Some things are consumed, produced, or transferred. In SysML v2, these are items.
A coffee machine deals with water, coffee beans, and brewed coffee. These are not parts of the machine – they flow through it.
package CoffeeMachineDomain {
import ScalarValues::*;
// Items: things that flow through the system
item def Water;
item def CoffeeBeans;
item def BrewedCoffee;
// Parts: structural components
part def Grinder {
attribute grindSize : Real;
}
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
}
part def WaterTank {
attribute currentLevel : Real;
attribute maxCapacity : Real;
}
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
attribute serialNumber : String;
}
}
item def Water is like part def Grinder, but it signals a different intent. Parts are components you assemble into a structure. Items are things that move, get consumed, or get produced. The distinction matters when you model behavior and flows in later chapters.
The // lines are comments. They are ignored by the checker but help readers navigate the model.
Step 7: An Enumeration
Some properties have a fixed set of valid values. The grind size might be Fine, Medium, or Coarse. SysML v2 models this with an enumeration:
package CoffeeMachineDomain {
import ScalarValues::*;
// Items
item def Water;
item def CoffeeBeans;
item def BrewedCoffee;
// Enumerations
enum def GrindSetting {
fine;
medium;
coarse;
}
// Parts
part def Grinder {
attribute grindSize : GrindSetting;
}
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
}
part def WaterTank {
attribute currentLevel : Real;
attribute maxCapacity : Real;
}
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
attribute serialNumber : String;
}
}
enum def GrindSetting defines a type with exactly three values. The Grinder attribute now uses GrindSetting instead of Real, which is more precise – a reviewer can see exactly what values are valid.
What You Have Built
With the definitions expanded, you can see every attribute inside each part definition. Notice how CoffeeMachine contains typed usages of the other parts, and each part definition lists its own attributes:
Step back and look at what you have. In about 40 lines, you have:
- A package that organizes the model
- Part definitions for three subsystems (Grinder, Brewer, WaterTank) and the top-level CoffeeMachine
- Item definitions for things that flow (Water, CoffeeBeans, BrewedCoffee)
- An enumeration for a constrained value set (GrindSetting)
- Attributes on each definition, typed with scalar values or enumerations
- Typed usages that compose the subsystems into the machine
- An import that brings scalar types into scope
This is already a useful structural model. A reviewer can see what the system is made of, what properties each component has, and how the components relate.
What Comes Next
This model is the foundation for the rest of the book. Every chapter will extend it:
- Chapter 3 formalizes the definition/usage pattern you used here.
- Chapter 4 shows how to split this model across multiple files.
- Chapter 5 adds ports – interaction points where components connect.
- Chapter 6 wires the components together.
- Chapter 7 adds behavior: what the machine does when you press brew.
- Chapter 8 adds operating modes: idle, brewing, cleaning, error.
- Chapter 9 adds checkable logic: temperature ranges, safety interlocks.
- Chapter 10 adds requirements and traces them to the design.
- Chapter 11 assembles everything into one coherent model.
Keep your coffee_machine.sysml file. You will keep building on it.
Common Mistakes
Forgetting the import. If you use Real or String without import ScalarValues::*, the checker will report unresolved names. The fix is always the same: add the import at the top of your package.
Leaving usages untyped. This is valid syntax:
part def CoffeeMachine {
part grinder;
}
But it tells a reviewer nothing about what kind of grinder this is. Always type your usages: part grinder : Grinder. Typed usages make the model checkable and reusable.
Defining everything at the top level. If you write definitions outside a package, they have no namespace boundary. As your model grows, name collisions become likely. Always wrap definitions in a package.
Definitions and Usages
In Chapter 2, you built a coffee machine model. Along the way, you wrote things like part def Grinder and part grinder : Grinder without much ceremony. This chapter explains why SysML v2 makes that split, what the different definition keywords mean, and how ownership works when you nest elements inside each other.
If you internalize one design rule from this book, make it this: definitions and usages are different jobs. A definition declares a reusable type. A usage places that type in a specific context. Every modeling decision you make in SysML v2 flows from that split.
Why the Split Exists
Consider the Grinder from Chapter 2:
part def Grinder {
attribute grindSize : GrindSetting;
}
part def CoffeeMachine {
part grinder : Grinder;
}
Grinder is a definition. It says: grinders exist, and they have a grind size. It does not say where a grinder lives, how many there are, or who owns one.
grinder is a usage. It says: inside a CoffeeMachine, there is one grinder, and it plays the role called grinder.
This separation is what makes the model reusable. Suppose you are modeling a coffee roastery that has a separate grinding station:
part def GrindingStation {
part grinder : Grinder;
part spareGrinder : Grinder;
}
The same Grinder definition appears in two completely different contexts – once inside CoffeeMachine, twice inside GrindingStation. The definition did not change. Only the usages differ.
Without this split, you would have to copy the grinder’s properties into every place it appears. Change the grind size attribute, and you would have to find and update every copy. The definition/usage pattern gives you a single source of truth.
The Definition Keywords
SysML v2 has several definition keywords, each signaling a different kind of thing. You have already seen three of them in Chapter 2. Here they are together:
part def – Structural Components
A part definition describes something you can assemble into a structure. Parts have spatial or compositional relationships with their containers. The grinder is inside the machine. The brewer is inside the machine. These are parts.
part def Grinder { ... }
part def Brewer { ... }
part def WaterTank { ... }
part def CoffeeMachine { ... }
Use part def when the thing you are describing is a component that belongs to an assembly. If you can point at it in a bill of materials or a physical breakdown, it is probably a part.
item def – Things That Flow
An item definition describes an identifiable thing the system acts on over time, without requiring that the thing perform actions of its own (SysML §7.10). Items flow through the system, get consumed, get produced, or get exchanged.
item def Water;
item def CoffeeBeans;
item def BrewedCoffee;
Water is not a part of the coffee machine. You do not bolt water onto the frame. But water flows into the tank, gets heated by the brewer, and comes out as brewed coffee. Modeling it as an item tells reviewers: this is something the system processes, not something it is made of.
Parts and items are not rival categories. A part definition is a kind of item definition (SysML §7.11), so every part can be treated as an item when that is the useful view – an engine flows down an assembly line as an item, then goes into a vehicle as a part. Choosing part def picks the primary framing; it does not forbid the other one.
The distinction becomes important when you add behavior in Chapter 7. Actions consume and produce items. Connections in Chapter 6 can carry items between ports. The item keyword sets up these downstream patterns.
attribute def – Properties and Value Types
An attribute definition describes a measurable or configurable property. You used attribute usages throughout Chapter 2 (grindSize, waterTemp, brewPressure), and you also defined one attribute type with enum def GrindSetting.
You can also define custom attribute types explicitly:
attribute def Temperature {
attribute value : Real;
attribute unit : String;
}
This defines Temperature as a structured value with a numeric part and a unit. You could then use it:
part def Brewer {
attribute waterTemp : Temperature;
attribute brewPressure : Real;
}
Use attribute def when you need a value type that has internal structure. For simple scalar properties, you can type attributes directly with Real, String, Integer, or Boolean from the ScalarValues library.
When to Use Which
| Keyword | What it models | Chapter 2 examples |
|---|---|---|
part def | Structural component | Grinder, Brewer, WaterTank, CoffeeMachine |
item def | Thing that flows | Water, CoffeeBeans, BrewedCoffee |
attribute def | Structured value type | (GrindSetting used enum def) |
enum def | Fixed set of values | GrindSetting |
If you are unsure, ask: “Is this thing a component of the assembly?” If yes, part def. “Does it flow through or get consumed?” If yes, item def. “Is it a measurable property?” If yes, attribute def or a scalar type.
You will meet more definition keywords in later chapters – action def for behavior in Chapter 7, state def for operating modes in Chapter 8, requirement def in Chapter 10. They all follow the same <keyword> def <Name> pattern.
How tools render this distinction
The SysML v2 graphical notation gives the def-vs-usage split a visual cue: definitions render with sharp corners, usages render with rounded corners. The same model appears as part def Grinder (a sharp-cornered box) and part grinder : Grinder (a rounded-cornered box) on every conformant diagram. You will see the convention applied throughout the diagram fixtures in this book and in any other SysML v2 tool you use.
Usages: Placing Types in Context
Every definition keyword has a corresponding usage keyword. You drop the def:
part def Grinder { ... } // definition
part grinder : Grinder; // usage
item def Water; // definition
item water : Water; // usage
attribute def Temperature; // definition
attribute temp : Temperature; // usage
A usage always answers three questions:
- What kind? The keyword (
part,item,attribute). - What name? The usage name (
grinder,water,temp). - What type? The definition after the colon (
Grinder,Water,Temperature).
The name is how you refer to this particular instance within its context. The type links back to the definition. A reviewer looking at part grinder : Grinder knows immediately what kind of thing it is and where to find its properties.
Untyped Usages
You can write a usage without a type:
part def CoffeeMachine {
part grinder;
}
This is syntactically valid. But it tells a reviewer nothing about what kind of grinder this is. There is no link to a definition, no properties to check, no way for a tool to validate that the usage makes sense. Treat untyped usages as temporary scaffolding. Always add a type before the model goes into review.
Reuse: One Definition, Many Usages
The real power of the definition/usage split shows up when you reuse definitions across contexts. Consider a company that makes two coffee machine models:
package CoffeeMachineLineup {
import CoffeeMachineDomain::*;
part def HomeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
attribute serialNumber : String;
}
part def OfficeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
part secondBrewGroup : Brewer;
attribute serialNumber : String;
}
}
Both machines use the same Grinder, Brewer, and WaterTank definitions from the CoffeeMachineDomain package you built in Chapter 2. The OfficeMachine happens to have a second brew group, but it reuses the same Brewer definition for both.
If you later add a descaleCount attribute to Brewer, both machines get it automatically. You do not touch HomeMachine or OfficeMachine at all. The definitions are the single source of truth; the usages just point at them.
This pattern also makes the model reviewable. When someone new reads part secondBrewGroup : Brewer, they can look up Brewer once and understand every place it appears. No hunting through duplicated property lists to check if they are all consistent.
Ownership and Membership
When you write an element inside another element’s braces, you are doing more than indenting text. You are declaring an ownership relationship.
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
}
The grinder usage is owned by the CoffeeMachine definition. This is called membership – grinder is a member of CoffeeMachine. Ownership has consequences:
- Scope. The name
grinderis visible insideCoffeeMachine. Code outsideCoffeeMachinemust use a qualified name likeCoffeeMachine::grinderto refer to it. - Lifecycle. A
partoritemusage declared like this is composite, and the values of a composite usage cannot exist after the instance that features them ceases to exist (SysML §7.6.3). Destroy aCoffeeMachine, and itsgrindergoes with it. - Containment. Composite parts are inside their owner, structurally. This is what makes
CoffeeMachinea composite: it contains its parts rather than merely knowing about them.
Ownership forms a tree. The package owns the definitions. Each definition owns its members. Each member might own further nested elements. This tree gives the model its structure and determines how names resolve.
Nesting Deeper
Ownership can go several levels deep. Suppose the grinder has an internal motor:
part def Motor {
attribute powerRating : Real;
}
part def Grinder {
attribute grindSize : GrindSetting;
part motor : Motor;
}
Now the ownership tree looks like this:
CoffeeMachineDomain (package)
Grinder (part def)
grindSize (attribute)
motor (part)
powerRating (attribute)
CoffeeMachine (part def)
grinder (part) : Grinder
brewer (part) : Brewer
...
Each element has exactly one owner. The motor usage is owned by Grinder. The grinder usage is owned by CoffeeMachine. The Grinder definition is owned by the CoffeeMachineDomain package. There is no ambiguity about where anything lives.
Composite vs. Reference Usages
Every part and item usage you have seen so far is composite – its values belong to the instance that features them and cannot outlive it. But sometimes you need to refer to something without containing it.
Consider a maintenance log that records which grinder was serviced:
part def MaintenanceRecord {
attribute date : String;
ref part servicedGrinder : Grinder;
}
The ref keyword says: this usage refers to a Grinder that exists somewhere else. The MaintenanceRecord does not contain a grinder. It just points at one.
Without ref, writing part servicedGrinder : Grinder makes the usage composite – the maintenance record would contain a grinder. That is clearly wrong. A maintenance record is a data entry, not a physical container for machine parts.
The distinction has teeth in the specification, not just in style. Values of a composite usage cannot also be values of another composite usage unless both sit on the same featuring instance (SysML §7.6.3) – a physical grinder is inside exactly one machine. Reference usages carry no such restriction, which is exactly why a record can point at a grinder some machine already contains. It is also why a bill-of-materials rollup counts composite usages and skips references.
Two kinds of usage are referential whether or not you write ref:
- Attribute usages. A value has no independent existence to contain, so
attribute grindSize : GrindSettingis always referential. - Directed usages. Anything declared
in,out, orinout– including the items inside the port definitions of Chapter 5 – is always referential.
Use ref when the relationship is “knows about” rather than “contains.” We will revisit reference usages when we cover connections in Chapter 6, where they play a larger role.
The Pattern Behind the Pattern
At this point, you have seen the definition/usage split applied to parts, items, and attributes. The pattern is always the same:
<keyword> def <Name> { <members> } // definition
<keyword> <name> : <Name>; // composite usage
ref <keyword> <name> : <Name>; // reference usage
This regularity is intentional. Every new SysML v2 concept you encounter – actions, states, requirements, constraints – follows this same structure. Once you can read part def and part, you can read action def and action, state def and state, requirement def and requirement. The keywords change; the pattern does not.
What Comes Next
You now understand the core modeling pattern of SysML v2: define a type, use it in context. You know the difference between parts (structural), items (flow), and attributes (values). You know that nesting creates ownership, and that ref makes a usage referential so it can point at something without containing it.
In Chapter 4, you will split the coffee machine model across multiple files, using packages and imports to organize definitions so that teams can work on different parts of the model independently.
Common Mistakes
Using part def for everything. Not every concept is a structural component. Water flowing through a brewer is an item, not a part. A temperature reading is an attribute, not a part. Using the right keyword communicates intent and enables correct downstream analysis.
Duplicating definitions instead of reusing them. If HomeMachine and OfficeMachine both need a brewer, define Brewer once and use it twice. Do not create HomeBrewer and OfficeBrewer with identical properties. Specialize only when behavior or structure genuinely differs – Chapter 12 covers how.
Forgetting ref on non-containing usages. If a MaintenanceRecord has part servicedGrinder : Grinder without ref, the model says the record physically contains a grinder. This is a modeling error that tools may not catch as a syntax problem – it is a semantic mistake. When the relationship is “refers to” rather than “contains,” always use ref.
Packages and Imports
The coffee machine model from Chapter 2 fits in a single file. That will not last. As soon as a second engineer needs to work on the model, or a second subsystem needs its own definitions, you need a way to split things up without breaking name resolution.
SysML v2 gives you two tools for this: packages for grouping and imports for connecting.
When One File Is Not Enough
Here is the model from Chapter 2, all in one file:
package CoffeeMachineDomain {
import ScalarValues::*;
item def Water;
item def CoffeeBeans;
item def BrewedCoffee;
enum def GrindSetting {
fine;
medium;
coarse;
}
part def Grinder {
attribute grindSize : GrindSetting;
}
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
}
part def WaterTank {
attribute currentLevel : Real;
attribute maxCapacity : Real;
}
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
attribute serialNumber : String;
}
}
This is 30 lines. But over the next several chapters, this model will grow to include ports, connections, actions, states, constraints, and requirements. One file will become hard to navigate and harder to review.
The fix is not to scatter definitions randomly across files. It is to decide what belongs together and draw clear boundaries.
Packages Are Namespace Boundaries
A package is a container for related model elements. Every name inside a package is scoped to that package. Two different packages can each define a Status enumeration without collision, because the full name includes the package: Brewing::Status versus Maintenance::Status.
You already used a package in Chapter 2. The new idea is using multiple packages to separate concerns.
Here is the coffee machine model split into two packages:
package CoffeeTypes {
import ScalarValues::*;
item def Water;
item def CoffeeBeans;
item def BrewedCoffee;
enum def GrindSetting {
fine;
medium;
coarse;
}
}
package CoffeeParts {
import ScalarValues::*;
import CoffeeTypes::*;
part def Grinder {
attribute grindSize : GrindSetting;
}
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
}
part def WaterTank {
attribute currentLevel : Real;
attribute maxCapacity : Real;
}
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
attribute serialNumber : String;
}
}
CoffeeTypes holds items and enumerations – domain vocabulary that does not depend on the machine structure. CoffeeParts holds the structural definitions and imports the types it needs.
The split follows a principle: separate what things are from how they are assembled.
Wildcard vs. Explicit Imports
The import CoffeeTypes::* line is a wildcard import. It brings every public member of CoffeeTypes into scope: Water, CoffeeBeans, BrewedCoffee, and GrindSetting all become directly usable names.
Wildcard imports are convenient when you are building a model and the package boundaries are still shifting. But they hide which names actually get used. If someone adds a Pressure attribute definition to CoffeeTypes later, it silently enters scope in CoffeeParts – possibly colliding with a local name.
An explicit import names exactly what you need:
package CoffeeParts {
import ScalarValues::Real;
import ScalarValues::String;
import CoffeeTypes::GrindSetting;
part def Grinder {
attribute grindSize : GrindSetting;
}
// ...
}
Now a reviewer can see at a glance: this package uses Real, String, and GrindSetting. Nothing else. If GrindSetting gets renamed or removed, this import line will fail and point you to the problem.
Use wildcard imports while exploring. Switch to explicit imports when the model stabilizes. Standard library packages like ScalarValues are a reasonable exception – wildcard-importing ScalarValues::* is common practice because those types rarely change and are used everywhere.
Qualified Names Without Importing
You do not always need an import. You can refer to any element by its fully qualified name:
package CoffeeParts {
part def Grinder {
attribute grindSize : CoffeeTypes::GrindSetting;
}
}
This is verbose but unambiguous. It works well for one-off references where adding an import would be noise. If you use the same qualified name three or more times, an import is cleaner.
Visibility: Public and Private Members
By default, every member of a package is public – visible to any package that imports it (KerML §7.2.5.2). You can make a member private so it is only visible inside its own package:
package CoffeeTypes {
import ScalarValues::*;
// Public: other packages can use these
item def Water;
item def CoffeeBeans;
item def BrewedCoffee;
enum def GrindSetting {
fine;
medium;
coarse;
}
// Private: internal helper, not part of the public API
private item def UsedGrounds;
}
UsedGrounds exists inside CoffeeTypes but will not appear in CoffeeParts even with a wildcard import. This is useful when a package has internal helpers that other packages should not depend on.
Think of visibility the same way you think about public and private functions in code. Expose what other packages need. Keep implementation details hidden.
There is a third keyword, protected. Outside a type it behaves exactly like private; inside a type it matters for what specializations inherit. Reach for it in Chapter 12 territory, not here.
Imports Have Visibility Too
The visibility keywords do not only apply to members. They apply to imports, and this is the part that surprises people.
[public | protected | private] import <Package>::<name-or-*>;
package Structure {
// Anyone importing Structure also gets these
public import Definitions::*;
// Structure can use these; importers of Structure cannot
private import Internal::*;
}
An import’s visibility decides whether the names it brings in are visible to your importers, or only to you. If the import is public, the imported memberships become public in the importing namespace – you have re-exported them. If it is private, they stay yours (SysML §7.5.3, KerML §7.2.5.4).
Read it as: public import forwards, private import consumes.
And the default is the one worth memorising, because it is the opposite of what
most people guess: a bare import is private. So this does not do what it
looks like:
package Structure {
import Definitions::*; // private -- NOT re-exported
}
A package that imports Structure gets nothing from Definitions. If you meant
to pass those names through, you have to say public import.
This is exactly why the coffee machine model’s package layout writes
public import in each of its concern packages: Structure, Behavior, and
QualityModel each re-export what they pull in, so a consumer imports one
package and gets a coherent set of names without knowing how the model is split
internally.
Importing a whole subtree
import <Package>::**;
Suffixing an import with ::** makes it recursive: it takes the namespace’s
members and then keeps going into nested namespaces (KerML §7.2.5.4). One **
replaces a column of imports when you genuinely want everything under a root.
package CoffeeMachineAnalysis {
import CoffeeMachineDomain::**;
}
Recursion stops at what was actually imported – it follows owned members of imported namespaces, and does not drag in memberships that arrived through implied specializations. Use it when you want a whole subtree and be aware that, like any wildcard, it makes the origin of a name harder to see at a glance.
Two forms the tool does not accept yet. The specification lets an import carry a body –
public import D::* { doc /* why */ }– and lets a namespace import be filtered by metadata, as inimport D::**[@Safety]. Both are in the grammar; sysml-rs parses neither, and for the body form it reports the error against the visibility keyword rather than the body, which is misleading. The semicolon forms above are what works today.
Nested Packages
Packages can contain other packages. This creates a hierarchy:
package CoffeeMachineDomain {
package Types {
import ScalarValues::*;
item def Water;
item def CoffeeBeans;
item def BrewedCoffee;
enum def GrindSetting {
fine;
medium;
coarse;
}
}
package Parts {
import ScalarValues::*;
import CoffeeMachineDomain::Types::*;
part def Grinder {
attribute grindSize : GrindSetting;
}
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
}
part def WaterTank {
attribute currentLevel : Real;
attribute maxCapacity : Real;
}
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
attribute serialNumber : String;
}
}
}
From outside the model, you would refer to CoffeeMachineDomain::Parts::CoffeeMachine. From inside Parts, you can refer to its own members directly.
To reach a sibling package, it helps to know how a name gets resolved. Resolution starts in the local namespace and then searches outwards through each containing namespace, up to the root and finally the global namespace (KerML §7.2.5.1). So from inside Parts, the name Types resolves – it is a member of the enclosing CoffeeMachineDomain – and Types::GrindSetting works with no import at all. What the import buys you is the unqualified name: without import CoffeeMachineDomain::Types::*, the bare name GrindSetting does not resolve inside Parts, because it is a member of the sibling package, not of the parent.
Nesting is useful for large models with several subsystem domains. For most models, one level of packages is enough. Do not nest deeper than two levels unless the model genuinely has that much structure.
When you want to see the nesting at a glance rather than read it from source, build a view def :> BrowserView over the package — BrowserView is one of the eight standard view definitions (SysML §9.2.20) covered in Chapter 13, and it presents the containment hierarchy as a tree.
Splitting Across Files
In SysML v2, a file is just a container for text. The language does not mandate a relationship between file names and package names. But a predictable convention saves time.
Here is a practical layout for the coffee machine model, with one package per file:
coffee-machine/
types.sysml -- CoffeeTypes package
parts.sysml -- CoffeeParts package
architecture.sysml -- top-level assembly (later chapters)
types.sysml:
package CoffeeTypes {
import ScalarValues::*;
item def Water;
item def CoffeeBeans;
item def BrewedCoffee;
enum def GrindSetting {
fine;
medium;
coarse;
}
}
parts.sysml:
package CoffeeParts {
import ScalarValues::*;
import CoffeeTypes::GrindSetting;
part def Grinder {
attribute grindSize : GrindSetting;
}
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
}
part def WaterTank {
attribute currentLevel : Real;
attribute maxCapacity : Real;
}
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
attribute serialNumber : String;
}
}
Nothing in the language ties an element to the file it was declared in. Names resolve by namespace, not by file position, so the order the files are loaded in does not matter.
Loading a whole directory is the tool’s job, not the language’s.
sysml check <file>takes a single file. To load every.sysmlfile in a directory and resolve names across them, reach forsysml inspect --workspace <dir>instead.
The convention is simple: one package per file, file name matches the package purpose. This makes it easy to find where a definition lives without searching. When you are ready to give your directory a formal identity with versioned dependencies, see Chapter 15.
What Changes, What Stays the Same
The model content has not changed. CoffeeMachine still contains a Grinder, a Brewer, and a WaterTank. The grinder still has a GrindSetting attribute. What changed is the organization: types in one package, parts in another, each in its own file.
This reorganization pays off starting in Chapter 5, when you add ports to the part definitions. You will import from CoffeeParts and CoffeeTypes rather than working inside one large package where everything is tangled together.
The browser view shows how the model’s packages and their contents form a tree:
Common Mistakes
Circular imports. If package A imports from B and B imports from A, you have a circular dependency. The specification explicitly permits circularity in imports (KerML §8.2.3.5.1), so this is legal and a conformant tool must cope with it – but it makes your model hard to understand and refactor. If two packages need each other, they probably belong in the same package, or you need to extract the shared elements into a third package.
Over-splitting. Creating a separate package for every single definition adds import noise without improving clarity. Group things that change together. If Grinder, Brewer, and WaterTank are always used as a set, keeping them in one package is better than scattering them across three.
Expecting a sibling package’s members to be in scope. A nested package does see the names its parent holds – resolution searches outwards – so Types, and anything else declared directly in CoffeeMachineDomain, resolves from inside Parts with no import. What you do not get is the sibling’s members: GrindSetting needs either the qualifier Types::GrindSetting or an import. Nesting gives you a naming hierarchy; it does not flatten one package’s members into another.
Structure
Your coffee machine model has definitions, usages, attributes, and an enumeration. It describes what things are and what properties they carry. But it says nothing about how those parts interact with the outside world – where water enters the brewer, where ground beans come out of the grinder, or how many heating elements exist. This chapter fills those gaps.
You will add ports to define interaction boundaries, multiplicity to say how many of something exist, and port definitions to type exactly what flows through each boundary. By the end, your coffee machine will have a fully specified structural interface.
Composite Structure Revisited
Before adding new concepts, look at what composite structure already gives you. In Chapter 2, you built this:
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
attribute serialNumber : String;
}
This says a CoffeeMachine is composed of three parts. The keyword part inside a definition creates an ownership relationship: the grinder, brewer, and water tank exist because the coffee machine exists. If you destroy the machine, the parts go with it. This is composite structure – the whole owns the parts.
Each part usage is typed. part grinder : Grinder does not just say “there is something called grinder.” It says the grinder conforms to the Grinder definition, which means it carries every attribute and port that Grinder declares. Type a usage, and all the definition’s features come along for free.
But right now, these three parts sit side by side inside CoffeeMachine with no way to interact. The grinder has no output chute. The brewer has no water inlet. To model interaction, you need ports.
Ports: Where Things Cross a Boundary
A port is a point on a part where interaction happens. Think of a physical connector, a pipe fitting, or an electrical terminal. In SysML v2, a port declares that something can flow into or out of a component at a specific point.
Here is the simplest possible port:
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
port waterInlet;
}
This adds an untyped port called waterInlet to the Brewer. It compiles, but it does not say anything useful. What flows through it? In what direction? To make ports meaningful, you need two things: a port definition that types the port, and a direction that says which way things flow.
Port Definitions
A port definition describes the kind of interaction point. It declares what items flow through it and in which direction.
port def WaterPort {
in item water : Water;
}
This says: a WaterPort is an interaction point where Water flows in. The direction keyword in is part of the item usage inside the port definition. Now apply it:
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
port waterInlet : WaterPort;
}
port waterInlet : WaterPort says: the brewer has an interaction point called waterInlet, and it conforms to the WaterPort definition. Because WaterPort declares in item water : Water, a reviewer (or a tool) can verify that anything connected to waterInlet must supply Water, flowing inward.
The pattern mirrors what you already know: port def defines a reusable type, port creates a usage of that type. Same definition/usage split as part def and part.
Direction: in, out, inout
Port definitions use direction keywords to declare which way items flow through a port. There are three:
in– the item flows into the owning partout– the item flows out of the owning partinout– the item flows in both directions
Direction is relative to the part that owns the port. When you write in item water : Water inside a port on the Brewer, you are saying water flows into the brewer through this port. From the perspective of whatever is connected on the other side, that same water is flowing out.
Here is what all three look like in port definitions:
port def WaterPort {
in item water : Water;
}
port def GroundCoffeePort {
out item grounds : CoffeeBeans;
}
port def StatusPort {
inout attribute level : Real;
}
WaterPort receives water. GroundCoffeePort emits ground coffee. StatusPort can both send and receive a level reading – useful for a sensor that reports values but can also be calibrated.
A single port definition can carry multiple items:
port def BrewInputPort {
in item water : Water;
in item grounds : CoffeeBeans;
}
This models a port where two distinct items enter. That is less common for physical ports (you would typically use separate connectors), but useful for logical interfaces where a single interaction point carries multiple signals.
Why Direction Matters
Direction is not decoration. It enables three things:
-
Connection validation. When you wire two ports together in Chapter 6, tools can check that an
outport connects to aninport of a compatible type. Connecting twoinports is a modeling error. -
Flow analysis. Direction tells you which way items move through the system. In Chapter 7, action flows will use port directions to determine data dependencies.
-
Interface contracts. A port definition is a contract: “this component expects to receive Water here.” Any component with a matching port can plug in. Direction is part of that contract.
If you omit direction, the item has no declared flow direction. That weakens every check that depends on knowing which way things move. Always declare direction on items inside port definitions.
Building Port Definitions for the Coffee Machine
Now apply ports to your existing model. Think about each component and ask: what crosses its boundary?
Grinder: Coffee beans go in; ground coffee comes out.
port def BeanInputPort {
in item beans : CoffeeBeans;
}
port def GroundCoffeePort {
out item grounds : CoffeeBeans;
}
Both ports carry CoffeeBeans, but at different stages. The beans that enter are whole; the grounds that exit have been processed. For now, both are typed as CoffeeBeans. In a more detailed model, you might define separate item types for whole beans and ground beans. Keep it simple until the model demands more precision.
Brewer: Water and ground coffee go in; brewed coffee comes out.
port def WaterPort {
in item water : Water;
}
port def BrewOutputPort {
out item coffee : BrewedCoffee;
}
WaterTank: Water goes out (to the brewer).
port def WaterSupplyPort {
out item water : Water;
}
Notice that WaterPort and WaterSupplyPort both involve Water, but in opposite directions. WaterPort receives water (in); WaterSupplyPort provides water (out). When you connect them in Chapter 6, the directions will match: one outputs what the other inputs.
Adding Ports to Existing Parts
With port definitions in hand, update the part definitions from Chapter 2. You are not redefining these parts – you are extending them with ports:
package CoffeeMachineDomain {
import ScalarValues::*;
// Items
item def Water;
item def CoffeeBeans;
item def BrewedCoffee;
// Enumerations
enum def GrindSetting {
fine;
medium;
coarse;
}
// Port definitions
port def BeanInputPort {
in item beans : CoffeeBeans;
}
port def GroundCoffeePort {
out item grounds : CoffeeBeans;
}
port def WaterPort {
in item water : Water;
}
port def WaterSupplyPort {
out item water : Water;
}
port def BrewOutputPort {
out item coffee : BrewedCoffee;
}
// Parts with ports
part def Grinder {
attribute grindSize : GrindSetting;
port beanInput : BeanInputPort;
port groundOutput : GroundCoffeePort;
}
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
port waterInlet : WaterPort;
port groundInput : BeanInputPort;
port brewOutput : BrewOutputPort;
}
part def WaterTank {
attribute currentLevel : Real;
attribute maxCapacity : Real;
port waterOut : WaterSupplyPort;
}
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
attribute serialNumber : String;
}
}
Look at what the model says now. The Grinder accepts beans through beanInput and emits grounds through groundOutput. The Brewer receives water and grounds, then outputs brewed coffee. The WaterTank supplies water. Every boundary is named, typed, and directional.
Notice that Brewer.groundInput reuses BeanInputPort. That is the same port definition used by Grinder.beanInput. Reuse is the point of port definitions: define the interface once, use it on any part that needs it. The Brewer accepts beans (ground) through the same type of input port as the Grinder, because structurally the interface is the same – coffee material flowing inward.
This general view shows the structural definitions with their ports and typing relationships:
sysml-rs extends the spec here. Container nodes in this book’s diagram fixtures (a
part defwith attributes inside, a port with a typed item flowing through it) can be expanded or collapsed. A collapsed node shows only its name and outline; an expanded node shows its members. The expand/collapse contract is a sysml-rs presentation convention — the spec graphical notation does not prescribe it. Reach for collapsed nodes when you want a high-level architectural read; expand them when you want the full attribute and port inventory.
Enumerations: A Brief Recap
Chapter 2 introduced enum def GrindSetting with three values. Enumerations are attribute definitions with a fixed set of members. A few additional patterns are worth knowing.
Enumeration values can carry data. The data is declared once, on an attribute definition, and each value fills it in:
attribute def BrewRatio {
attribute ratio : Real;
}
enum def BrewStrength :> BrewRatio {
enum light { :>> ratio = 15.0; }
enum medium { :>> ratio = 12.0; }
enum strong { :>> ratio = 10.0; }
}
BrewStrength specializes BrewRatio, so it inherits ratio; each value then redefines ratio with its own number. Any attribute typed by BrewStrength can be asked for its ratio, because there is one ratio feature rather than three.
Resist the temptation to declare attribute ratio : Real inside the enumeration definition itself. Setting aside annotations – a doc or a comment is always welcome – an enumeration definition’s body holds only its enumeration values (SysML §7.8.2). The definition is a set of alternatives, not a place to hang features. Declaring a fresh ratio inside each value instead would parse, but it would give you three unrelated attributes and nothing common to query.
You can reference enum members using qualified names:
BrewStrength::strong
GrindSetting::fine
This is the same :: scoping syntax you use with packages and imports. An enum definition is a namespace; its members are named elements inside that namespace.
For most modeling work, simple enumerations like GrindSetting are sufficient. Use attributed enumerations when the domain genuinely needs data attached to each variant.
Multiplicity
So far, every usage in the model means “exactly one.” The coffee machine has one grinder, one brewer, one water tank. But real systems are not always that simple. A machine might have multiple heating elements, zero or more optional accessories, or a sensor array with a specific count.
Multiplicity declares how many instances of a usage can exist. You write it in square brackets, most commonly right after the type – though the specification also allows it directly after the name, or after a :> or :>> clause, and only one multiplicity per usage (SysML §7.6.3):
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
part heatingElement : HeatingElement [2];
port waterInlet : WaterPort;
port groundInput : BeanInputPort;
port brewOutput : BrewOutputPort;
}
part heatingElement : HeatingElement [2] says: every Brewer contains exactly two heating elements. Not one, not three – two.
Multiplicity Notation
The general form is [lower..upper]. Common patterns:
| Notation | Meaning |
|---|---|
[1] | Exactly one (common explicit singleton intent) |
[2] | Exactly two |
[0..1] | Optional – zero or one |
[1..*] | One or more |
[0..*] | Any number, including zero |
[2..4] | Between two and four |
The * means “unbounded.” [0..*] is the most permissive multiplicity: any number of instances.
When You Omit Multiplicity
If you write part grinder : Grinder with no brackets, you almost always get [1..1] anyway. The specification declares an implicit [1..1] when all three of these hold (SysML §7.6.3):
- the usage is an attribute usage, an item usage (including a part usage, but not a connection usage), or a port usage;
- it is owned by a definition or another usage – not directly by a package;
- and it declares no subsetting or redefinition of its own.
Every usage in the coffee machine model meets all three, so the model already means “exactly one” everywhere. When those conditions do not hold – a usage sitting directly in a package, or one that subsets an inherited collection – the usage takes the bounds of whatever it subsets or redefines, falling back to the most general [0..*] if nothing tighter is inherited.
Writing [1] therefore changes nothing about the meaning. It makes the intent visible:
part def CoffeeMachine {
part grinder : Grinder [1];
part brewer : Brewer [1];
part waterTank : WaterTank [1];
}
This is more verbose but leaves nothing to a reader’s memory of the default rule. Use explicit multiplicity when the count matters for analysis, and leave it off when “one” is obvious from context.
Multiplicity on Attributes and Ports
Multiplicity is not limited to parts. Attributes and ports support it too:
part def SensorArray {
attribute readings : Real [0..*];
port outlets : PowerOutlet [6];
}
readings carries zero or more values. outlets provides exactly six connection points. You will not need multi-valued attributes or ports for the coffee machine, but the syntax is the same wherever multiplicity appears.
Attribute Definitions and Default Values
Chapter 2 used Real, String, and GrindSetting as attribute types. Two additional patterns are worth knowing before you see the full model.
Attribute definitions create reusable value types, the same way part def creates reusable component types:
attribute def TemperatureRange {
attribute min : Real;
attribute max : Real;
}
Then use it on a part: attribute operatingTemp : TemperatureRange. This bundles related properties into a single named attribute instead of scattering loose fields.
Values attach a number to an attribute, and there are three ways to do it that mean three different things:
part def WaterTank {
attribute currentLevel : Real := 0.0;
attribute maxCapacity : Real default = 1500.0;
attribute nominalPressure : Real = 1.0;
port waterOut : WaterSupplyPort;
}
:=is an initial value.currentLevelstarts at zero and changes as the tank fills.default =is an overridable default.maxCapacityis 1500.0 unless a particular usage says otherwise. Writingdefault 1500.0, with the=elided, means the same thing.- A bare
=is a binding. It asserts that the attribute is that value, for every instance, at all times.
The bare = is the one that catches people out. attribute currentLevel : Real = 0.0 does not say “starts empty” – it asserts the level is always zero (SysML §7.6.3). Reach for = when the value is a genuine constant of the type, := when it is a starting point, and default = when a usage should be free to override it. For computed values derived from other properties, see Chapter 9.
The Complete Model So Far
Here is the full coffee machine model after this chapter. Everything from Chapter 2 is preserved; ports, port definitions, and explicit multiplicity are the additions:
package CoffeeMachineDomain {
import ScalarValues::*;
// Items: things that flow through the system
item def Water;
item def CoffeeBeans;
item def BrewedCoffee;
// Enumerations
enum def GrindSetting {
fine;
medium;
coarse;
}
// Port definitions: typed interaction boundaries
port def BeanInputPort {
in item beans : CoffeeBeans;
}
port def GroundCoffeePort {
out item grounds : CoffeeBeans;
}
port def WaterPort {
in item water : Water;
}
port def WaterSupplyPort {
out item water : Water;
}
port def BrewOutputPort {
out item coffee : BrewedCoffee;
}
// Structural definitions with ports
part def Grinder {
attribute grindSize : GrindSetting;
port beanInput : BeanInputPort;
port groundOutput : GroundCoffeePort;
}
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
port waterInlet : WaterPort;
port groundInput : BeanInputPort;
port brewOutput : BrewOutputPort;
}
part def WaterTank {
attribute currentLevel : Real := 0.0;
attribute maxCapacity : Real default = 1500.0;
port waterOut : WaterSupplyPort;
}
part def CoffeeMachine {
part grinder : Grinder [1];
part brewer : Brewer [1];
part waterTank : WaterTank [1];
attribute serialNumber : String;
}
}
Count the ports. The grinder has two (beans in, grounds out). The brewer has three (water in, grounds in, coffee out). The water tank has one (water out). Together, these ports define every boundary where material crosses from one subsystem to another.
The CoffeeMachine itself does not have ports yet. That is intentional – the machine’s external interface (where the user puts beans in, where the cup sits) is a design decision for later. Right now, the internal boundaries are what matter for wiring subsystems together.
What Comes Next
The parts have boundaries, but nothing connects them. The grinder’s groundOutput and the brewer’s groundInput are compatible – they both involve CoffeeBeans flowing in matching directions – but the model does not say they are wired together.
Chapter 6 adds connections and interfaces to wire these ports together, completing the structural picture. The ports you defined here are the endpoints; Chapter 6 provides the wires.
Common Mistakes
Forgetting direction on port items. This compiles:
port def WaterPort {
item water : Water;
}
But without in, out, or inout, tools cannot validate connections or analyze flow direction. Always specify direction.
Defining ports without port definitions. You can write port waterInlet; with no type, but that tells tools nothing about what flows through it. An untyped port is like an untyped attribute – technically valid, practically useless.
Mismatched directions at connection time. If you define WaterPort as in and WaterSupplyPort as in, you cannot connect them – both sides are trying to receive. One side must output what the other inputs. Think of directions as pipe fittings: a supply end connects to a receiving end.
Over-specifying multiplicity. Writing [1] on every single usage adds noise without information when the count is obvious. Use explicit multiplicity when it conveys design intent: [2] for dual heating elements, [0..1] for optional accessories, [1..*] for extensible arrays. Leave it off when “one” is self-evident.
Connections and Interfaces
Your coffee machine has parts and ports. The WaterTank has an outlet, the Brewer has inlets, the Grinder has an output. But right now, nothing is wired together. The ports exist in isolation – interaction points with nothing to interact with.
This chapter connects the machine. You will start with the simplest possible connection, add port-level wiring, define interface contracts, and finally model what actually flows through those wires. By the end, the coffee machine will be a connected system, not just a bag of parts.
The Simplest Connection
A connection in SysML v2 says: these two parts interact. The simplest form names the two ends directly:
part def CoffeeMachine {
part waterTank : WaterTank;
part brewer : Brewer;
connect waterTank to brewer;
}
connect waterTank to brewer creates a connector between the two part usages. It says the water tank and brewer are linked somehow, but it does not say how. No ports are involved. No item type is specified. It is a statement of topology: these two parts interact.
This is useful early in modeling when you want to capture the fact that two subsystems communicate before you have worked out the details. A reviewer can see the interaction structure without getting buried in port definitions.
But there is a problem. The connection says nothing about where on each part the interaction happens, or what moves between them. For a coffee machine, that matters. Water enters the brewer through a specific inlet, not through the grinder’s bean hopper. To express that, you need port-level connections.
Connecting Through Ports
In Chapter 5, you added ports to the coffee machine’s parts. The WaterTank has a waterOut port that sends Water. The Brewer has a waterInlet that receives Water and a groundInput port that receives ground CoffeeBeans. The Grinder has a groundOutput that sends ground CoffeeBeans.
Now connect them at the port level:
part def CoffeeMachine {
part waterTank : WaterTank;
part brewer : Brewer;
part grinder : Grinder;
connect waterTank.waterOut to brewer.waterInlet;
connect grinder.groundOutput to brewer.groundInput;
}
This is more precise. connect waterTank.waterOut to brewer.waterInlet says: the water tank’s waterOut port is connected to the brewer’s waterInlet port. The dot notation navigates from the part usage to its port.
Two things changed from the simple form:
- Each end is a port, not a part. The connection targets the specific interaction point on each component.
- Direction matters implicitly.
waterOutis typed byWaterSupplyPort, which declaresout item water;waterInletis typed byWaterPort, which declaresin item water. The connection goes from a source of Water to a sink of Water. SysML v2 does not force you to declare the direction on the connector itself – the port definitions already carry that information.
This is the form you will use most often. Port-level connections make the interaction topology explicit and checkable. A tool can verify that the types on each end are compatible: an outlet sending Water connects to an inlet receiving Water.
Naming Your Connections
Every connection so far has been anonymous. You can give a connector a name if you want to refer to it later – in constraints, requirements, or documentation:
part def CoffeeMachine {
part waterTank : WaterTank;
part brewer : Brewer;
part grinder : Grinder;
connection waterLine connect waterTank.waterOut to brewer.waterInlet;
connection beanFeed connect grinder.groundOutput to brewer.groundInput;
}
waterLine and beanFeed are now named connectors. The names do not change behavior, but they make the model more readable and give you handles to attach flows, constraints, or requirements to specific connections.
Interface Definitions
A connection says two ports are linked. An interface definition says what a valid connection between two kinds of ports looks like. It is a contract.
Suppose you want to formalize what it means to connect a water source to a water consumer. Define an interface:
interface def WaterSupply {
end supplierPort : WaterSupplyPort;
end consumerPort : WaterPort;
}
interface def WaterSupply declares a reusable connection contract with two ends. Each end is typed by a port definition. Any connection that matches this pattern – a WaterSupplyPort on one side, a WaterPort on the other – conforms to the WaterSupply interface.
You use the interface when creating the connection:
part def CoffeeMachine {
part waterTank : WaterTank;
part brewer : Brewer;
interface waterLine : WaterSupply {
end supplierPort ::> waterTank.waterOut;
end consumerPort ::> brewer.waterInlet;
}
}
This says: the connection between waterTank.waterOut and brewer.waterInlet is an instance of the WaterSupply interface. The ::> binds each interface end to a specific port usage.
When do you need interface definitions? Not always. For simple models, direct connect statements are fine. Interface definitions earn their keep when:
- Multiple connections follow the same pattern and you want to enforce consistency.
- You need to attach shared constraints or metadata to a class of connections.
- You are modeling a system where the interface contract matters independently of who implements it – like a standardized connector.
Item Flows
Connections and interfaces describe the topology: what is linked to what. Item flows describe the content: what actually moves through a connection.
You already defined item types in Chapter 2 – Water, CoffeeBeans, BrewedCoffee. An item flow says that a specific item type travels along a specific connection:
part def CoffeeMachine {
part waterTank : WaterTank;
part brewer : Brewer;
part grinder : Grinder;
connection waterLine connect waterTank.waterOut to brewer.waterInlet;
connection beanFeed connect grinder.groundOutput to brewer.groundInput;
flow of Water from waterTank.waterOut to brewer.waterInlet;
flow of CoffeeBeans from grinder.groundOutput to brewer.groundInput;
}
flow of Water from waterTank.waterOut to brewer.waterInlet says: Water moves from the tank’s outlet to the brewer’s inlet. The of Water clause types the flow. The from ... to ... clause identifies the source and destination.
Item flows serve a different purpose than connections. A connection says two ports are linked. A flow says something specific travels through that link. You can have a connection without a flow (the link exists, but you have not specified what moves), and in principle you can have multiple flows on the same connection (water and dissolved minerals, say).
In practice, most connections carry one primary flow, and you will often write both the connection and the flow together. The flow is what makes reviewers confident the model is complete: not just “these parts are linked” but “Water moves from here to there.”
Named Flows
Like connections, flows can be named:
flow hotWater of Water from waterTank.waterOut to brewer.waterInlet;
flow groundBeans of CoffeeBeans from grinder.groundOutput to brewer.groundInput;
Named flows are useful when you need to reference them in constraints or requirements. For example, a requirement might say “the hotWater flow shall not exceed 95 degrees Celsius.” Without a name, you have nothing to point at.
Flow Definitions
Every flow so far has been a usage written directly where it happens. Flows have
definitions too, the same way parts and actions do – and if you find yourself
writing the same of <Item> from <port> to <port> shape repeatedly, that is the
signal to define it once.
flow def <Name> {
end <name> : <SourcePortType>;
end <name> : <TargetPortType>;
}
flow def WaterFlow {
end supply : WaterSupplyPort;
end intake : WaterPort;
}
part def CoffeeMachine {
part waterTank : WaterTank;
part brewer : Brewer;
flow supplyLine : WaterFlow from waterTank.waterOut to brewer.waterInlet;
}
A flow definition is a reusable kind of flow. Its end members say what the
two sides must be – here, that a WaterFlow always runs from something with a
WaterSupplyPort to something with a WaterPort. The usage then names the
particular flow and says which ports it connects.
Read it as: WaterFlow is the kind of movement; supplyLine is this
movement, in this machine.
This is the def/usage split from Chapter 3 applied to flows, and it buys the same things it always does. The definition is one place to document what the flow means, one place to attach a constraint on its rate or temperature, and one thing a requirement can point at for every machine rather than for one instance.
The coffee machine’s own model defines WaterFlow, CoffeeFlow and SteamFlow
this way, and then declares usages against them.
Flows that also order
part def CoffeeMachine {
succession flow waterThenCoffee of Water
from waterTank.waterOut to brewer.waterInlet;
}
A succession flow does two jobs at once: it carries something, and it
constrains time. The plain flow says water moves from the tank to the brewer.
succession flow adds that the source end completes before the target end
begins – the ordering meaning you met as first ... then in
Chapter 7, attached to a flow rather than to two actions.
Reach for it when the sequence matters as much as the transfer: the tank finishes delivering before the brewer starts drawing, rather than both being live at once.
Binding Connectors
There is one more kind of connector you should know about: the binding connector. A regular connection says two separate things interact. A binding says two things are the same thing, seen from different contexts.
Suppose you expose a port on the CoffeeMachine itself, and you want to bind it to an internal part’s port:
part def Brewer {
port waterInlet : WaterPort;
port groundInput : BeanInputPort;
port brewOutput : BrewOutputPort;
}
part def CoffeeMachine {
port dispenserOut : BrewOutputPort;
part brewer : Brewer;
bind dispenserOut = brewer.brewOutput;
}
bind dispenserOut = brewer.brewOutput says: the machine’s dispenserOut port is the brewer’s brewOutput port. They are not separate endpoints with something flowing between them. They are the same port, exposed at two levels of the hierarchy. What appears on brewer.brewOutput appears on dispenserOut, by identity.
Bindings are how you thread internal ports up to an external interface. If the CoffeeMachine is used inside a larger system – a cafe counter, say – that outer system connects to dispenserOut without knowing about the brewer inside.
Allocation
Every relationship so far has been structure-to-structure: this port talks to that port, this item moves along that link. Allocation is the one that crosses between models. It says a behavioral element is the responsibility of a structural one – this step of the brew cycle is the grinder’s job.
You need it because the two decompositions are deliberately independent (Chapter 7 makes that point about actions). BrewCycle breaks into grind, heat, and extract; CoffeeMachine breaks into grinder, waterTank, and brewer. Nothing in either decomposition says which part does which step. Allocation is where you write that down:
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
perform action brewCycle : BrewCycle;
// Commit each step of the cycle to the part responsible for it
allocate brewCycle.grind to grinder;
allocate brewCycle.heat to waterTank;
allocate brewCycle.extract to brewer;
}
allocate brewCycle.grind to grinder says: whatever the grind step requires, the grinder is what has to deliver it. That is a claim a reviewer can check. If grind needs 200 W and the grinder is specified at 150 W, the allocation is where the mismatch surfaces – not in a meeting six months later. Allocations are also queryable in both directions: “what does the brewer have to do?” and “who is responsible for heating?” both have answers in the model.
When you need to attach anything to the allocation itself – a rationale, a margin, a review status – name it and give it a definition, exactly as with connections:
allocation def FunctionToComponent;
part def CoffeeMachine {
part grinder : Grinder;
perform action brewCycle : BrewCycle;
allocation grindAlloc : FunctionToComponent allocate brewCycle.grind to grinder;
}
Reach for the bare allocate when the mapping is the whole story, and the named form when the mapping itself needs properties. Allocation is not limited to behavior-to-structure: the same construct maps requirements to components, logical designs to physical ones, or software functions to processors. The pattern is always “this element is realized by that one”.
The Connected Coffee Machine
Here is the coffee machine with all of its connections and flows in one place. This builds on the ports from Chapter 5 and the definitions from Chapter 2:
package CoffeeMachineConnections {
import CoffeeMachineDomain::*;
part def CoffeeMachine {
// Internal parts
part waterTank : WaterTank;
part brewer : Brewer;
part grinder : Grinder;
// External port
port dispenserOut : BrewOutputPort;
// Port-level connections
connection waterLine connect waterTank.waterOut to brewer.waterInlet;
connection beanFeed connect grinder.groundOutput to brewer.groundInput;
// Typed flows -- what moves through each connection
flow hotWater of Water from waterTank.waterOut to brewer.waterInlet;
flow groundBeans of CoffeeBeans from grinder.groundOutput to brewer.groundInput;
// Binding -- expose brewer output as machine output
bind dispenserOut = brewer.brewOutput;
}
}
Read this model from top to bottom. The parts tell you what the machine is made of. The connections tell you how the parts are wired. The flows tell you what moves through those wires. The binding tells you where the output appears to the outside world.
A reviewer looking at this model can answer: What are the subsystems? How do they connect? What flows between them? Where does the product come out? That is a complete structural interaction picture, and it took about 15 lines.
The Progression
This chapter followed a deliberate progression that mirrors how you should model connections in practice:
- Simple connect.
connect tank to brewer– capture the topology before the details. - Port-level connect.
connect waterTank.waterOut to brewer.waterInlet– specify exactly where interaction happens. - Named connection.
connection waterLine connect ...– give handles for later reference. - Interface definition.
interface def WaterSupply– formalize reusable connection contracts. - Item flow.
flow of Water from ... to ...– specify what moves. - Binding.
bind dispenserOut = brewer.brewOutput– expose internal ports to the outside. - Allocation.
allocate brewCycle.grind to grinder– commit behavior to the structure responsible for it.
You do not need all seven levels for every connection. Many models get by with port-level connections and typed flows. Interface definitions and bindings appear when the model needs to formalize contracts or manage hierarchy. Start simple and add precision where the design demands it.
Visualizing Connections
This interconnection view shows how the parts connect through their ports, with item flows indicating what travels through each connection:
This is the Interconnection view — one of the eight standard view definitions covered in Chapter 13. If you are coming from SysML v1, you will recognize the same content under the older labels: a part-level Interconnection is what v1 called an Internal Block Diagram (IBD), and a definition-level Interconnection is what v1 called a Block Definition Diagram (BDD).
sysml-rs extends the spec here. When the connection sits between two ports whose types are quantities from the standard library’s ISQ family — voltage, current, force, velocity, and so on — the simulation runtime treats the connection as a bond in a bond graph, with conservation laws applied automatically. The bond-graph synthesis is a sysml-rs simulation behaviour, not a spec construct; see Appendix E (the physics-aware simulation reference) once you reach the simulation chapters.
Common Mistakes
Connecting definitions instead of usages. This is wrong:
connect WaterTank to Brewer; // Wrong: these are definitions, not usages
Connections link usages – the specific parts inside a context. You cannot connect WaterTank as a type; you connect waterTank, the specific instance inside CoffeeMachine.
Mismatched port types on a connection. If waterTank.waterOut sends Water but brewer.groundInput expects CoffeeBeans, connecting them is a type error. The port types on each end of a connection should be compatible. This is one of the main benefits of typed ports – the model catches wiring mistakes.
Flows without connections. A flow says what moves, but a connection says what is linked. If you write a flow between two ports that have no connection, you are asserting transfer through a link that does not exist. Always establish the connection first, then add the flow.
Using bind when you mean connect. A binding says two things are identical. A connection says two separate things interact. If the water tank and brewer are separate parts with different ports, you want connect. If you are exposing an internal port at a higher level in the hierarchy, you want bind.
What Comes Next
The coffee machine now has structure, ports, connections, and flows. It is fully wired but entirely static. In Chapter 7, you will add behavior – the brew cycle that moves water through the tank, grinds beans, and produces coffee. The connections you built here become the pathways that behavior uses.
Actions
Up to this point, the coffee machine model describes what the system is – parts, attributes, ports, connections. But a machine that only has structure does nothing. This chapter adds behavior: what the system does when someone presses the brew button.
In SysML v2, behavior is modeled with actions. An action definition describes a reusable unit of behavior, the same way a part definition describes a reusable structural component. Actions compose into workflows through sequencing, parallelism, branching, and signaling.
Defining an Action
An action definition declares a kind of behavior. It follows the same def pattern you have seen with parts and items:
action def GrindBeans;
That says: there is a kind of behavior called GrindBeans. It does not say when or where it happens. It is a type, not an occurrence.
To say that something performs this behavior, you create an action usage inside another action:
action def GrindBeans;
action def HeatWater;
action def BrewCycle {
action grind : GrindBeans;
action heat : HeatWater;
}
action grind : GrindBeans says: inside a BrewCycle, there is a step called grind, and it is of type GrindBeans. This is the same definition/usage pattern from Chapter 2, applied to behavior instead of structure.
Without anything else, the two sub-actions have no defined order. SysML v2 does not assume sequential execution. You have to say it.
Sequential Composition
The first ... then statement declares that one action must complete before another begins:
action def BrewCycle {
action grind : GrindBeans;
action heat : HeatWater;
first grind then heat;
}
first grind then heat creates a succession – an ordering constraint that says grind finishes before heat starts. This is a directed relationship between two action usages, not a property of either action by itself.
You can chain successions to build longer sequences:
action def BrewCycle {
action grind : GrindBeans;
action heat : HeatWater;
action extract : ExtractCoffee;
action dispense : DispenseCoffee;
first grind then heat;
first heat then extract;
first extract then dispense;
}
That reads: grind, then heat, then extract, then dispense. Each first ... then is a separate succession statement. Together they form a chain.
Every action has two implicit endpoints: start and done. You can anchor your sequence to them:
action def BrewCycle {
action grind : GrindBeans;
action heat : HeatWater;
action extract : ExtractCoffee;
action dispense : DispenseCoffee;
first start then grind;
first grind then heat;
first heat then extract;
first extract then dispense;
first dispense then done;
}
first start then grind says that grind is the first thing that happens when a BrewCycle begins. first dispense then done says the cycle finishes after dispensing completes. These endpoints make the boundary of the action explicit – a reviewer can see exactly where behavior begins and ends.
You can also chain successions on a single line using the then keyword after an action usage:
action def BrewCycle {
action grind : GrindBeans;
then action heat : HeatWater;
then action extract : ExtractCoffee;
then action dispense : DispenseCoffee;
}
The then before an action usage is shorthand: it creates a succession from the previously declared action to the current one. This form is more compact but means the same thing as writing separate first ... then statements.
Naming a succession
Everything so far has written the ordering inline, inside the action body. There is a second form that names it:
succession [<name>] [first] <source> then <target>;
action def BrewCycle {
action grind : GrindBeans;
action heat : HeatWater;
action extract : Extract;
succession grindDone first grind then heat;
succession heat then extract;
}
Read the first one as: this ordering constraint is called grindDone, and it
says grind comes before heat.
Both lines declare the same kind of relationship the bare first ... then does.
What the keyword buys you is a name, and a named succession is a model
element like any other – you can document it, refer to it from a requirement, or
allocate it. That is why larger models tend to use this form for the orderings
that matter and the bare form for the rest.
Note that first is optional here. succession heat then extract; and
succession first heat then extract; mean the same thing – the specification says
so directly: “If a succession declaration includes only the related features part,
then the keyword first can be omitted” (KerML §7.4.6.4). The coffee machine’s own
orchestration model uses the shorter spelling throughout.
One more thing that clause tells you, and it is worth knowing because it explains
what a succession actually is: if you give a succession no explicit type, it is
implicitly typed by the library association HappensBefore, which lives in the
standard library’s Occurrences package. An ordering constraint
is not a special syntactic device bolted onto actions – it is a connector, typed
from the standard library, whose two ends happen to be things ordered in time.
Action Parameters
Actions often need inputs and produce outputs. You declare these with in and out parameters:
action def GrindBeans {
in item beans : CoffeeBeans;
out item grounds : GroundCoffee;
}
in item beans : CoffeeBeans says that GrindBeans receives coffee beans as input. out item grounds : GroundCoffee says it produces ground coffee as output. The item keyword here means the parameter carries an item – something that flows through the system.
Parameters make the interface of an action explicit. When you compose actions, the outputs of one step can connect to the inputs of the next:
action def GrindBeans {
in item beans : CoffeeBeans;
out item grounds : GroundCoffee;
}
action def ExtractCoffee {
in item grounds : GroundCoffee;
in item water : HotWater;
out item coffee : BrewedCoffee;
}
A reviewer can see at a glance what each step needs and what it produces, without reading the internals.
Parallel Composition with Fork and Join
Not everything in a workflow happens one step at a time. In the coffee machine, heating water and grinding beans can happen simultaneously – the grinder and the heater are independent subsystems.
SysML v2 models parallelism with fork and join nodes. A fork splits one flow into multiple concurrent paths. A join waits for all concurrent paths to complete before continuing.
action def BrewCycle {
action grind : GrindBeans;
action heat : HeatWater;
action extract : ExtractCoffee;
action dispense : DispenseCoffee;
first start then fork forkNode;
first forkNode then grind;
first forkNode then heat;
first grind then join joinNode;
first heat then joinNode;
first joinNode then extract;
first extract then dispense;
first dispense then done;
}
Here is what happens at runtime:
- The cycle starts.
forkNodesplits execution into two concurrent paths.grindandheatrun in parallel.joinNodewaits until bothgrindandheatfinish.extractruns after the join.dispenseruns after extraction.- The cycle ends.
The fork keyword declares a fork node – a control node with no behavior of its own. Its job is to start multiple paths at the same time. The join keyword declares a join node that synchronizes those paths back together. Think of a fork as a split and a join as a rendezvous.
Fork and join nodes are not actions with duration. They are instantaneous control points. The real work happens in the action usages (grind, heat, extract, dispense). The control nodes just direct traffic.
Decisions and Merges
Sometimes a workflow needs to choose between paths based on a condition. SysML v2 models this with decide and merge nodes.
A decision node picks one outgoing path. A merge node brings multiple alternative paths back together. Where fork/join models “do all of these,” decide/merge models “do one of these.”
Suppose the coffee machine can make either espresso or drip coffee, depending on a setting:
action def BrewCycle {
in attribute espressoMode : Boolean;
action grind : GrindBeans;
action heat : HeatWater;
action espressoExtract : ExtractEspresso;
action dripExtract : ExtractDrip;
action dispense : DispenseCoffee;
first start then grind;
first grind then heat;
first heat then decide decideMethod;
if espressoMode then espressoExtract;
else dripExtract;
first espressoExtract then merge mergeMethod;
first dripExtract then mergeMethod;
first mergeMethod then dispense;
first dispense then done;
}
After heating, the decideMethod node evaluates the condition. If espressoMode is true, execution follows the path to espressoExtract. Otherwise, it follows the path to dripExtract. Only one path runs.
The mergeMethod node reunites the two alternatives. Whichever extraction method was chosen, execution continues to dispense after the merge. The merge does not wait for both paths – only one path is active, and the merge simply accepts whichever one arrives.
This is the key difference between fork/join and decide/merge:
- Fork/join: all paths execute concurrently, join waits for all.
- Decide/merge: one path executes based on a condition, merge accepts whichever arrives.
The guard condition (if espressoMode) is attached to the outgoing succession from the decision node. The else keyword marks the default path taken when no guard is satisfied.
Combining Control Flow
Real workflows combine sequencing, parallelism, and decisions. Here is a more complete brew cycle that uses all three:
package CoffeeMachineActions {
import ScalarValues::*;
import CoffeeMachineDomain::*;
action def GrindBeans {
in item beans : CoffeeBeans;
}
action def HeatWater {
in item water : Water;
}
action def ExtractEspresso;
action def ExtractDrip;
action def DispenseCoffee {
out item coffee : BrewedCoffee;
}
action def BrewCycle {
in attribute espressoMode : Boolean;
in item beans : CoffeeBeans;
in item water : Water;
out item coffee : BrewedCoffee;
action grind : GrindBeans {
in item beans = BrewCycle::beans;
}
action heat : HeatWater {
in item water = BrewCycle::water;
}
action espressoExtract : ExtractEspresso;
action dripExtract : ExtractDrip;
action dispense : DispenseCoffee {
out item coffee = BrewCycle::coffee;
}
// Grind and heat in parallel
first start then fork forkPrep;
first forkPrep then grind;
first forkPrep then heat;
first grind then join joinPrep;
first heat then joinPrep;
// Choose extraction method
first joinPrep then decide decideMethod;
if espressoMode then espressoExtract;
else dripExtract;
first espressoExtract then merge mergeMethod;
first dripExtract then mergeMethod;
// Dispense and finish
first mergeMethod then dispense;
first dispense then done;
}
}
Read the control flow section from top to bottom and you get the full story: the cycle starts by forking into parallel preparation (grind and heat). After both finish, a decision selects the extraction method. After extraction, the results merge and the coffee is dispensed.
The action parameters (in item beans, out item coffee) make the data flow explicit alongside the control flow. The binding in item beans = BrewCycle::beans connects the sub-action’s input to the enclosing action’s parameter. This is how data threads through a composed workflow.
Send and Accept
Actions can communicate with each other through signals. The send action transmits a signal, and the accept action waits for one to arrive. This is how you model event-driven behavior – one part of the system notifying another that something happened.
action def NotifyReady {
send BrewComplete() to display;
}
send BrewComplete() to display says: transmit a BrewComplete signal to the display port (or part). The signal is an item that carries information about an event.
On the receiving side:
action def WaitForBrew {
accept brewDone : BrewComplete;
}
accept brewDone : BrewComplete says: pause and wait until a BrewComplete signal arrives. When it does, execution continues and the signal is available as brewDone.
You can use accept inside an action body to model a step that blocks until an event occurs:
action def ServeCustomer {
accept orderReceived : BrewRequest;
then action brew : BrewCycle;
then action notify send BrewComplete() to customerDisplay;
}
This reads: wait for a brew request, then perform the brew cycle, then notify the customer display. The accept at the beginning means the action does not start its real work until the triggering event arrives.
Routing a message through a specific port
When a part has more than one port, a plain send ... to <part> leaves the channel unspecified. The via keyword routes a signal along a particular port so the model says which connection carries it:
action def RouteSignalThroughPort {
send LowWaterSignal() via communicationPort;
}
send LowWaterSignal() via communicationPort says: route the LowWaterSignal through the communicationPort, not whichever other port the part happens to have. This matters when a part exposes several ports and the routing decision is part of the model, not left to the runtime.
On the receiving side you can select an incoming channel with via as well:
action def RouteAcceptThroughPort {
accept alert : FaultSignal via communicationPort;
}
accept alert : FaultSignal via communicationPort says: wait for a FaultSignal arriving on the communicationPort. The via keyword selects which incoming channel the message comes from, so an accept with a port filter only fires for messages on that port.
Send and accept are the bridge between structure and behavior. The structural model defines ports and connections (Chapter 6). The behavioral model uses send and accept to say what signals travel through those connections and when.
Connecting Actions to Structure
An action definition describes behavior in the abstract. To say which part of the system performs it, you use perform inside a part definition:
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
perform action brewCycle : BrewCycle;
}
perform action brewCycle : BrewCycle says: a CoffeeMachine performs a BrewCycle. This connects the structural model (what the machine is made of) with the behavioral model (what the machine does). You can also allocate individual sub-actions to specific parts, saying that the grinder performs the grinding and the brewer performs the extraction – that is what allocate is for, covered in Chapter 6.
This separation is deliberate. The action decomposition (BrewCycle contains grind, heat, extract, dispense) is independent of the structural decomposition (CoffeeMachine contains grinder, brewer, waterTank). You can change the action decomposition without touching the structure, and vice versa. The perform relationship is the bridge between the two.
Actions That Modify State
So far every action the coffee machine has performed only reads its inputs and produces outputs – the model describes data flowing through the workflow, but nothing changes a value mid-flight. Real behavior is messier: an action increments a brew counter, flips a mode flag, or aborts a cycle when a sensor trips. SysML v2 has constructs for all three – assignments, bidirectional parameters, loops, and early termination – and they all live inside action bodies, the same bodies you have been writing.
Assignment
An assignment mutates a value as the workflow runs. It binds an expression to an attribute or a parameter reference with the := operator:
assign <target> := <expression>;
Here is a brew cycle that records how many brews it has run:
action def BrewCycle {
inout attribute brewCycles : Integer;
assign brewCycles := brewCycles + 1;
}
assign brewCycles := brewCycles + 1 says: take the current value of brewCycles, add one to it, and write the result back. This is an assignment – it runs when the action body executes, not when the model is parsed. It is different from declaring an attribute with an initial value (which is set once when the part is created), and different from a calc (which is a pure function of its inputs). An assignment is a side effect on a binding the caller owns.
That is why the target has to be something the action can reach back out to. Writing to a plain in parameter would be pointless – the caller would never see the change. The next section makes the mechanics explicit.
inout parameters
Parameters come in three directions. in carries a value into the action; out carries a value out of it. inout does both: the caller supplies an initial value, the action can read it and write it back:
action def ToggleMode {
inout attribute espressoMode : Boolean;
}
inout attribute espressoMode : Boolean says: the caller hands the action the current value of espressoMode, and any change the action makes is visible to the caller when the action completes. Inside the action body, espressoMode reads like an in and writes like an out.
Use inout whenever the natural model of the behavior is “update this thing in place” rather than “take an old value, return a new one”. A mode toggle, a running counter, a shared status flag – all of these want inout, because the same binding carries the value in and the updated value out.
While loops
Not every workflow is a fixed sequence of steps. Sometimes you need to keep doing something as long as a condition holds. A while loop runs its body repeatedly until a guard becomes false:
while <condition> { ... }
Stir the water until it reaches the target temperature:
action def HeatAndStir {
in attribute targetTemp : Real;
inout attribute currentTemp : Real;
while currentTemp < targetTemp {
action stir : Stir;
assign currentTemp := currentTemp + 1.0;
}
}
while currentTemp < targetTemp { ... } says: as long as currentTemp is below targetTemp, keep running the actions inside the braces. The condition is evaluated at the start of each iteration; when it becomes false, the loop exits and execution continues after the closing brace.
Reach for while when the number of iterations is not known up front – a polling loop, a feedback controller, a “keep trying until the sensor is happy” pattern. If you already know how many times to run, the next loop is clearer.
For loops
When you have a collection and want to do something once per element, a for loop iterates over it:
for <item> in <collection> { ... }
Process each brew request in a queue:
action def ProcessBrewQueue {
in attribute brewRequests : BrewRequest[0..*];
out attribute successCount : Integer;
assign successCount := 0;
for request in brewRequests {
action brew : BrewCycle {
in item beans = request.beans;
}
assign successCount := successCount + 1;
}
}
for request in brewRequests { ... } says: bind request to each element of brewRequests in turn and run the body once per element. Inside the body, request refers to the current element. The loop runs over the whole sequence; when it is done, execution continues after the loop.
for is the loop to reach for when the iteration is driven by data – one pass per item in a collection – rather than by a runtime condition. It keeps the “does this run the right number of times?” question off the reviewer’s plate: the answer is “once per element”.
A tool caveat. sysml-rs currently reports
AX009 -- for loop has no collection referenceon everyforloop, including this one. The construct is specification syntax and the example is correct; the tool is not yet resolving the collection. If you see AX009, you have not mistyped anything.
Terminate
A workflow should be able to abort early when something goes wrong. The terminate action stops the enclosing action immediately and returns control to the caller:
terminate;
Abort a brew if the temperature is unsafe:
action def BrewCycle {
in attribute temperature : Real;
if temperature > 105.0 {
send FaultSignal() to panelPort;
terminate;
}
then action extract : ExtractCoffee;
}
terminate; says: stop this action right now. No further actions in the current scope run – the extract step below the guard never executes. The fault signal has already been sent; the caller is handed back control with the action in an aborted state.
Use terminate for hard cutoffs: a safety interlock that must not let the rest of the cycle proceed, a validation failure that makes the rest of the work meaningless. It is the action equivalent of an early return – reach for it when nothing later in the body should run.
The Story So Far
The coffee machine now has behavior. Here is what the model captures after this chapter:
- Action definitions for each step:
GrindBeans,HeatWater,ExtractEspresso,ExtractDrip,DispenseCoffee. - A composed workflow (
BrewCycle) that sequences, parallelizes, and branches those steps. - Parameters that thread data (beans, water, coffee) through the workflow.
- Control flow with fork/join for parallelism and decide/merge for branching.
- Send and accept for event-driven communication between parts.
- Message routing with
via <port>to direct signals through a specific channel. - Assignment (
:=) andinoutparameters for state a workflow mutates in place. - Loops –
whilefor condition-driven repetition,forfor collection iteration. - Terminate for an early, clean abort of an action body.
- Perform to connect actions to the parts that execute them.
The next chapter, Chapter 8, builds on this foundation. Where actions describe what the machine does during a single brew, states describe which mode the machine is in over time: idle, brewing, cleaning, error. Actions will appear again as entry, do, and exit behaviors attached to states.
Common Mistakes
Forgetting to sequence sub-actions. If you write two action usages without any first ... then between them, SysML v2 does not assume they run in order. They have no defined ordering at all. Always add explicit successions.
Confusing fork/join with decide/merge. A fork starts all outgoing paths concurrently. A decision picks exactly one. If you use a fork where you meant a decision, your model says the system does both things at the same time – which may not be what you intended. Likewise, a join waits for all incoming paths. A merge just accepts whichever single path arrives. Mixing them up changes the semantics of your workflow.
Writing one massive action body. It is tempting to put all behavior into a single action definition with dozens of sub-actions and complex control flow. Break large actions into smaller named definitions. Each definition should represent a meaningful step that can be reviewed and discussed independently. Compose them in a top-level action the way you compose parts in a top-level structure.
States
Your coffee machine has structure – parts, ports, connections – and behavior – the BrewCycle action that grinds, heats, extracts, and dispenses. But an action describes what happens. It does not describe when the machine is allowed to do it.
A real coffee machine has operating modes. It sits idle until someone presses a button. It brews, then returns to idle. It enters a cleaning cycle periodically. It shuts down into an error mode when something goes wrong. These modes govern which actions are permitted and which transitions between modes are allowed.
SysML v2 models operating modes with states and transitions. This chapter adds a MachineStates state definition to the coffee machine, covering state definitions and usages, transitions with guards and triggers, entry and exit actions, and nested states.
Defining States
A state definition declares a reusable operating mode, the same way a part definition declares a reusable component type. The pattern should look familiar:
state def Idle;
state def Brewing;
These are definitions – blueprints for modes that can be used inside a larger state machine. By themselves, they do not say which system uses them or how they connect.
To build a state machine, you create a composite state definition that contains state usages and transitions between them:
state def MachineStates {
state idle : Idle;
state brewing : Brewing;
transition first idle then brewing;
}
The pattern mirrors what you saw with parts. state def Idle defines a type. state idle : Idle creates a usage of that type inside MachineStates. The transition says: from idle, proceed to brewing.
The keyword first in transition first idle then brewing identifies the source state. The keyword then identifies the target. Read it as: “transition, starting from idle, then go to brewing.”
Building the Coffee Machine States
The coffee machine needs four operating modes: idle (waiting for input), brewing (making coffee), cleaning (maintenance cycle), and error (fault detected). Start with the definitions and a containing state machine:
package CoffeeMachineStates {
state def Idle;
state def Brewing;
state def Cleaning;
state def Error;
state def MachineStates {
state idle : Idle;
state brewing : Brewing;
state cleaning : Cleaning;
state error : Error;
transition first idle then brewing;
transition first brewing then idle;
transition first idle then cleaning;
transition first cleaning then idle;
}
}
Here is what this state machine looks like in an interactive state-transition view. Each state is a rounded block, and the arrows show which transitions are possible:
This is a valid state machine, but it is too permissive. Nothing prevents the machine from transitioning to brewing when there is no water, or from leaving error without intervention. Every transition fires unconditionally. You need guards.
Guards
A guard is a boolean condition on a transition. The transition only fires when the guard evaluates to true. In SysML v2, you write a guard with the if keyword after the source state:
transition first <source> if <condition> then <target>;
Add guards to the coffee machine transitions. Assume the machine has boolean attributes that represent sensor readings:
package CoffeeMachineStates {
import ScalarValues::*;
attribute def StartBrewSignal;
attribute def StartCleanSignal;
state def Idle;
state def Brewing;
state def Cleaning;
state def Error;
state def MachineStates {
attribute waterAvailable : Boolean;
attribute cupDetected : Boolean;
attribute brewComplete : Boolean;
attribute cleanComplete : Boolean;
attribute faultDetected : Boolean;
state idle : Idle;
state brewing : Brewing;
state cleaning : Cleaning;
state error : Error;
transition first idle
if waterAvailable and cupDetected
then brewing;
transition first brewing
if brewComplete
then idle;
transition first idle
if cleanComplete == false
then cleaning;
transition first cleaning
if cleanComplete
then idle;
transition first idle
if faultDetected
then error;
transition first brewing
if faultDetected
then error;
}
}
Now the idle-to-brewing transition only fires when water is available and a cup is detected. The brewing-to-idle transition only fires when the brew cycle has completed. And any state can reach error when a fault is detected.
Guards make transition intent explicit. A reviewer reading this model can see exactly what conditions must hold for each mode change. This is especially valuable for safety-critical transitions – you do not want the machine to start brewing with no cup in place.
Triggers
Guards check conditions, but they do not say what event initiates the transition. A trigger specifies the event that causes a transition to fire. In SysML v2, triggers use the accept keyword:
transition first <source> accept <event> then <target>;
The event is typically an attribute definition that represents a signal. You define the signal type, then reference it in the transition:
attribute def StartBrewSignal;
state def MachineStates {
state idle : Idle;
state brewing : Brewing;
transition first idle
accept StartBrewSignal
if waterAvailable and cupDetected
then brewing;
}
Read this as: “When the machine is in idle and receives a StartBrewSignal, if water is available and a cup is detected, transition to brewing.”
Triggers and guards work together. The trigger says what event to respond to. The guard says whether the response is allowed. You can have a trigger without a guard (always respond to the event), a guard without a trigger (transition when the condition becomes true), or both.
Time-driven triggers: accept after
Some transitions fire on the passage of time rather than on an external signal. The spec gives you accept after(<duration>) for that case, with duration literals using SI time-units:
transition first heating accept after(30 [s]) then ready;
transition first cooling accept after(500 [ms]) then off;
transition first warming accept after(t_dead) then idle; // any Real attribute typed as a duration
The unit literal can be s, ms, us, ns, min, h — anything in the Time quantity kind from the ISQ standard library. The duration argument is a real expression: a literal like 30 [s], a model attribute like t_dead, or a calc result.
accept after is one member of a small family of trigger constructors from the spec stdlib (Triggers.kerml). The other two move the triggering event off the passage of time and onto a moment or a condition.
Condition triggers: when
A condition trigger fires when a Boolean condition becomes true, rather than when an external signal arrives. The spec constructor is TriggerWhen; in the textual form you write it on the transition as a when clause:
transition first <source> accept when <condition> then <target>;
Stand down the brewer once the water is hot enough:
transition first brewing accept when waterTemp >= 92.0 then ready;
accept when waterTemp >= 92.0 says: while the machine is in brewing, watch waterTemp; the instant the condition changes from false to true, the transition fires. Note the difference from a guard. A guard (if) is evaluated at the moment a trigger event occurs and decides whether the response is allowed. A when trigger is the event – it watches the condition continuously and fires the transition the moment it becomes true, with no external signal involved.
sysml-rs extends the spec here. The
whenconstruct is spec language. What the runtime adds is the precision: when the condition mentions a continuously-varying state variable, the runtime watches that variable and fires the transition the instant the guard becomes true, with sub-tick precision via zero-crossing detection. The spec says the condition must become true; sysml-rs’s runtime says exactly when. See Chapter 16.
Absolute-time triggers: at
after is relative – d time units elapse in the source state. at is absolute – the transition fires at a specific moment on the clock. The spec constructor is TriggerAt:
transition first <source> accept at <time-expression> then <target>;
Start the clean cycle on a schedule:
attribute cleanTime : Time;
transition first idle accept at cleanTime then cleaning;
accept at cleanTime says: when the machine is in idle, fire the transition at the clock instant named by cleanTime. cleanTime is a point on the timeline, not a duration – so at is what you reach for when the trigger is a deadline or a schedule, where after is what you reach for when the trigger is “wait, then go”.
Transitions on port messages
Every trigger so far listens for a signal by name. But the signal can also be a message arriving on a specific port. The via keyword (the same one you saw on action send/accept in Chapter 7) routes the trigger to a particular channel:
transition first <source> accept <name> : <Signal> via <port> then <target>;
Suppose the brewer exposes a dispensePort and emits CupReady on it when a cup is in place. The machine dispenses only when that specific port reports readiness:
port def DispensePort {
out item ready : CupReady;
}
part def Brewer {
port dispensePort : DispensePort;
}
transition first brewing
accept cup : CupReady via brewer.dispensePort
then dispensing;
accept cup : CupReady via brewer.dispensePort says: wait for a CupReady message arriving on brewer.dispensePort, and bind it to cup when it arrives. The via <port> clause makes the trigger precise about which connection carries the message, instead of listening for any CupReady in the machine’s namespace.
Port-message triggers pay off in component architectures where several parts emit the same signal type on different ports; via is how you say which one this transition is waiting on.
Entry actions as full expressions
The body of an entry or exit action does not have to be a plain reference to an action declared elsewhere. It can be an inline action with a body of its own – an assignment, a nested step, or a sequence of these – so you can drive state-local computation from the state itself rather than through a separate action def.
state Brewing {
entry action setPower { assign brewer.heaterPower := 1500.0; }
exit action clearPower { assign brewer.heaterPower := 0.0; }
}
When the entry/exit logic is reused across states, lift it into an action def. When it is a single in-place setting or a one-line calc, write it inline.
Entry and Exit Actions
When the machine enters a state, it often needs to do something – turn on a heater, start a timer, display a status message. When it leaves a state, it may need to clean up – turn off a pump, reset a counter. SysML v2 models these with entry and exit actions inside a state.
action def LogMessage;
action def StartHeating;
action def StopHeating;
state def Brewing {
entry action : StartHeating;
exit action : StopHeating;
}
The entry action runs every time the machine enters the Brewing state. The exit action runs every time it leaves. These are guarantees: no matter which transition enters Brewing, the heater starts. No matter which transition leaves, the heater stops.
There is also a do action, which runs continuously while the state is active:
action def MonitorTemperature;
state def Brewing {
entry action : StartHeating;
do action : MonitorTemperature;
exit action : StopHeating;
}
The do action is the ongoing behavior of the state. Think of entry as setup, do as the main work, and exit as teardown.
The Full Coffee Machine State Model
Putting it all together – states, transitions with guards and triggers, and entry/exit actions:
package CoffeeMachineStates {
import ScalarValues::*;
// Signal definitions for triggers
attribute def StartBrewSignal;
attribute def StartCleanSignal;
attribute def FaultSignal;
attribute def ResetSignal;
// Action definitions for entry/exit
action def LogStatus;
action def StartHeating;
action def StopHeating;
action def RunCleanCycle;
action def StopCleanCycle;
action def DisableOutputs;
// State definitions with entry/exit behavior
state def Idle {
entry action : LogStatus;
}
state def Brewing {
entry action : StartHeating;
exit action : StopHeating;
}
state def Cleaning {
entry action : RunCleanCycle;
exit action : StopCleanCycle;
}
state def Error {
entry action : DisableOutputs;
}
// The composite state machine
state def MachineStates {
attribute waterAvailable : Boolean;
attribute cupDetected : Boolean;
attribute brewComplete : Boolean;
attribute cleanComplete : Boolean;
state idle : Idle;
state brewing : Brewing;
state cleaning : Cleaning;
state error : Error;
// Start in idle
entry; then idle;
// Idle -> Brewing: user requests brew, preconditions met
transition first idle
accept StartBrewSignal
if waterAvailable and cupDetected
then brewing;
// Brewing -> Idle: brew cycle finished
transition first brewing
if brewComplete
then idle;
// Idle -> Cleaning: user requests cleaning
transition first idle
accept StartCleanSignal
then cleaning;
// Cleaning -> Idle: clean cycle finished
transition first cleaning
if cleanComplete
then idle;
// Any operational state -> Error: fault detected
transition first idle
accept FaultSignal
then error;
transition first brewing
accept FaultSignal
then error;
transition first cleaning
accept FaultSignal
then error;
// Error -> Idle: operator resets
transition first error
accept ResetSignal
then idle;
}
}
Several things to notice:
entry; then idle;at the top ofMachineStatesdeclares the initial state. When the state machine starts, it entersidlefirst.- Each state definition carries its own entry/exit behavior.
Brewingalways starts the heater on entry and stops it on exit, regardless of which transition enters or leaves. - The
FaultSignaltransitions are repeated for each source state. In SysML v2, each transition has exactly one source and one target. If three states can reacherror, you write three transitions. - The
ResetSignaltransition fromerrorback toidleis the only way out of the error state. This is a deliberate design choice – the operator must explicitly acknowledge and reset the fault.
Nested States
States can contain substates. When the machine is brewing, it might go through sub-modes: heating, extracting, and dispensing. You model this by putting state usages inside a state definition:
state def Brewing {
entry action : StartHeating;
state heating;
state extracting;
state dispensing;
entry; then heating;
transition first heating
then extracting;
transition first extracting
then dispensing;
exit action : StopHeating;
}
From the outside, the machine is in Brewing. From the inside, it moves through heating, extracting, and dispensing as substates. When the final substate completes, the Brewing state itself completes, and the outer state machine transitions to whatever follows.
Nested states are useful when a single operating mode has internal phases, but the rest of the system only needs to know about the outer mode. Keep nesting shallow – one level of substates covers most real systems. Deep nesting makes models hard to review.
Parallel States
Recall the closing remark from the full model above: “The FaultSignal transitions are repeated for each source state. If three states can reach error, you write three transitions.” That repetition is not just tedious – it is fragile. Add a fourth operational state, and you have to remember to give it a FaultSignal transition too. Miss one, and your machine can sit in that state forever ignoring a fault.
The reason this hurts is that the model treats a fault as something each operational mode has to anticipate individually. But a fault is not a property of brewing or cleaning – it is a property of the machine as a whole, one that should be able to interrupt whichever mode is active. SysML v2 has a construct for exactly this: a state that contains parallel regions – independent sub-machines that run concurrently inside one parent state.
Defining orthogonal regions
A region is written as a state usage nested inside a composite state, just like the nested substates you have already seen. Two things are different: you include more than one, each with its own initial transition, and you mark the parent parallel. That keyword is not decoration, and the specification is explicit about what it switches: with isParallel true the owned states are all performed in parallel, and with it false only one owned state may be performed (SysML §8.3.18.5):
state def <Parent> parallel {
state <regionA> {
/* one sub-machine: states + transitions + its own entry; then ... */
}
state <regionB> {
/* a second sub-machine, running concurrently with regionA */
}
}
The parent state def contains two named sub-machines. Entering the parent enters both; neither waits on the other. Read it as: “while the machine is in <Parent>, region A and region B are each doing their own thing, in parallel.”
One constraint comes with parallel, and the specification names it. Under validateStateDefinitionParallelSubactions (SysML §8.3.18.5): if a state definition is parallel, its owned actions – which includes its owned states – must have no incoming and no outgoing transitions. Every transition a region declares stays inside that region. This is what makes the regions orthogonal – you cannot transition from a substate of one region to a substate of another, which is exactly the independence the next section relies on.
Splitting the fault out of the operational modes
Apply this to the coffee machine. Give MachineStates two regions: an operational region that runs idle, brewing, and cleaning, and a supervision region that just listens for a fault and can move the whole machine to error:
state def MachineStates parallel {
attribute waterAvailable : Boolean;
attribute cupDetected : Boolean;
// Region 1: the operational lifecycle
state operational {
state idle : Idle;
state brewing : Brewing;
state cleaning : Cleaning;
entry; then idle;
transition first idle
accept StartBrewSignal
if waterAvailable and cupDetected
then brewing;
transition first brewing if brewComplete then idle;
transition first idle accept StartCleanSignal then cleaning;
transition first cleaning if cleanComplete then idle;
}
// Region 2: fault supervision, running concurrently
state supervision {
state armed;
state error : Error;
entry; then armed;
transition first armed accept FaultSignal then error;
transition first error accept ResetSignal then armed;
}
}
Now the fault lives in one place. The supervision region has a single FaultSignal transition, regardless of how many operational modes exist in the other region. Add a fifth or sixth operational state to the operational region and you do not touch the fault handling at all.
The trade-off is that the two regions are independent. A region does not know which substate of its sibling is active. If the fault recovery needed to behave differently depending on whether the machine was brewing or cleaning, a single supervision region would not be enough – you would be back to per-mode transitions (or you would model that knowledge a different way). Reach for parallel regions when a concern truly cuts across all the sibling substates, not when it branches on them.
Completion
SysML v2 has no final marker on a state. What it has instead is a pair of boundary states that every state inherits. States::StateAction, the standard library’s base type for every state definition, declares ref state start and ref state done – the starting and ending snapshots of the state’s performance. You mark completion by transitioning to done:
state def OneShot {
state warmup;
state ready;
entry; then warmup;
transition first warmup accept ReadySignal then ready;
transition first ready then done;
}
The last transition says: once ready has been reached, the enclosing OneShot state is finished. That is the same shape the standard library uses for its own state machines – Actions.sysml writes transition aTransition first start accept apayload: Anything via receiver then done;. start and done are ordinary inherited features, so you reference them by name like any other substate; there is no keyword to remember.
In the MachineStates model above, nothing transitions to done, so the machine never completes on its own – which is what you want for a machine that runs until it is switched off.
Resuming an interrupted region
When a region is interrupted – by a fault, by its parent being exited and re-entered, or by a transition that points at the composite from the outside – it restarts from its initial substate. Sometimes that is wrong. A brew interrupted by a clean cycle should, on returning to brewing, pick up where it left off, not restart grinding.
SysML v2 has no history pseudostate. If you are coming from SysML v1 or UML, this is one of the constructs that did not carry over: there is no shallow or deep history marker in the language, and no library element that stands in for one. Resuming is something you model explicitly.
The straightforward way is to keep the last active substate in an attribute and branch on it when the region is re-entered:
state def BrewPhases {
attribute resumeAt : String;
state dispatch;
state grinding { entry action markGrinding { assign resumeAt := "grinding"; } }
state extracting { entry action markExtracting { assign resumeAt := "extracting"; } }
entry; then dispatch;
// Re-entry branches on the recorded phase instead of always restarting
transition first dispatch if resumeAt == "extracting" then extracting;
transition first dispatch if resumeAt != "extracting" then grinding;
transition first grinding accept PhaseDone then extracting;
}
Each phase records that it was entered; a small dispatch state reads that record and routes to the right phase using ordinary guarded transitions. It is more verbose than a history marker, but the state being remembered is now a visible part of the model – which is what a reviewer needs anyway. Deep history has no shorthand either: nest the same pattern, one attribute per level.
sysml-rs extends the spec here. Parallel regions are spec –
parallelis a real modifier on a state definition or usage. What the runtime adds on top is the scheduling: when an event arrives, the runtime advances each region to a stable point before delivering the next external event, so every region sees a consistent snapshot of the others. That run-to-completion guarantee is the sysml-rs execution rule; the spec says the regions are performed concurrently, not how a tool must drain their internal queues.One caveat if you are following along with the tool: sysml-rs does not yet parse the
parallelkeyword, so the model above will not check. The keyword is specification syntax and the example is correct – the gap is on the tool’s side.
Completion Transitions
Every transition so far has an explicit trigger – a signal, a duration, a condition, a port message. But a state that runs a do behavior has one more way to leave: it can finish. A completion transition fires when the source state’s do behavior completes, with no trigger written at all:
transition first <source> then <target>;
The absence of a trigger is the trigger. The Brewing state from the nested-states section runs MonitorTemperature as its do action; when that action ends, the state is done, and a completion transition can carry the machine onward. Like every transition, it connects state usages, so it lives in the containing state machine:
state def Brewing {
entry action : StartHeating;
do action : MonitorTemperature;
exit action : StopHeating;
}
state def Dispensing;
state def BrewSequence {
state brewing : Brewing;
state dispensing : Dispensing;
transition first brewing then dispensing; // fires when brewing's `do` completes
}
Read it as: “when the brewing state is done, go to dispensing.” No accept, no when, no after – the completion of the do behavior is the implicit event.
Completion is stricter in a parallel composite: the parent is not finished while any region is still running. Since there is no final marker in the language, a region signals that it is finished the way any state does – by transitioning to its inherited done state, as in the OneShot example above.
Exhibiting State Machines on Parts
So far the state machine has lived in its own state def MachineStates, separate from the parts it governs. SysML v2 also lets a part carry its own state machine inline, with the exhibit keyword.
part def <Part> {
exhibit state <name> { <states and transitions> };
}
Give the brewer its own small lifecycle, owned by the part rather than declared outside it:
part def Brewer {
exhibit state brewingMode {
state heating;
state ready;
entry; then heating;
transition first heating accept when waterTemp >= 92.0 then ready;
}
}
exhibit state brewingMode { ... } says: every Brewer instance runs its own brewingMode state machine. The states and transitions live inside the part definition, so a reviewer reading Brewer sees the part’s structure and its lifecycle in one place.
Exhibiting is the structural counterpart to the parallel regions from earlier. Parallel regions live inside a composite state and share one machine. An exhibit lives on a part and is owned by that part – the part’s internal modes are its own API, and other parts reach them by sending signals to that part. Reach for exhibit when a component’s modes are nobody else’s business; reach for a shared state def when the modes are a property of the system the parts belong to.
States and Actions Together
States and actions serve different roles. Actions describe what happens – the sequence of steps in a brew cycle. States describe when things are allowed – the machine must be in Idle before it can start brewing.
In a complete model, you connect them. The do action inside a state can reference an action definition:
action def BrewCycle {
action grind : GrindBeans;
action heat : HeatWater;
action extract : ExtractCoffee;
action dispense : DispenseCoffee;
first grind then heat then extract then dispense;
}
state def Brewing {
entry action : StartHeating;
do action : BrewCycle;
exit action : StopHeating;
}
When the machine enters Brewing, it runs StartHeating, then executes BrewCycle as its ongoing behavior, and runs StopHeating when it leaves. The state governs the lifecycle; the action governs the procedure.
sysml-rs extends the spec here. A state machine can also gate continuous behavior, not just discrete actions. When a model carries an ODE — see Chapter 9’s
:> GetDerivativepattern and the worked hybrid example in Chapter 16 — entering one state can switch which derivative is integrated. The runtime monitors transition guards that mention continuous state variables and fires those transitions the instant the guard becomes true, with sub-tick precision. This is what lets a thermostat trip cleanly at 100°C and a bouncing ball bounce at exactly y=0. The state-machine half is spec; the zero-crossing-as-transition mechanism is sysml-rs’s runtime contribution.
Common Mistakes
Forgetting the initial state. A composite state definition without entry; then <state>; has no defined starting point. Reviewers and tools will not know which substate activates first. Always declare the initial transition.
Omitting guards on safety-critical transitions. An unguarded transition from idle to brewing means the machine can start brewing at any time – no water check, no cup check. If a transition has safety implications, give it a guard. The guard makes the precondition visible in the model, not hidden in implementation code.
Using states where actions belong. If a “state” has no transitions into or out of it and just runs a fixed sequence of steps, it is probably an action, not a state. States model modes that the system can be in. Actions model procedures that the system performs. The test is: can the system stay in this mode indefinitely, waiting for an event? If yes, it is a state. If it always runs to completion, it is an action.
What You Have Built
With the state definitions expanded, you can see the entry and exit actions inside each state, plus all the transitions with their guards and triggers:
The coffee machine now has a complete lifecycle model. MachineStates defines four operating modes with guarded, triggered transitions between them. Entry and exit actions ensure consistent setup and teardown. The initial state is explicit, the error state requires a deliberate reset, and the guard on idle to brewing enforces preconditions that a reviewer can inspect.
In Chapter 9, you will add constraints – checkable logic that makes properties like temperature ranges and safety interlocks part of the model itself.
Expressions and Constraints
So far, the coffee machine model describes what the system is and what it does. It has structure (parts, ports, connections), behavior (actions with control flow), and modes (states with guarded transitions). What it cannot do yet is answer a quantitative question: Is the brew temperature correct? Is it safe to dispense?
This chapter adds checkable logic. You will write expressions that compute values, define calculations that can be reused, and create constraints that the model must satisfy. By the end, the coffee machine will have a temperature target calculation, a safety interlock constraint, and the language vocabulary you need to read every expression in the book.
Expression Basics
An expression in SysML v2 is any combination of values and operators that produces a result. You have already seen simple expressions in attribute defaults and transition guards. Now we look at the full operator set.
The everyday operators come in four families:
Arithmetic: +, -, *, /
Relational: <, >, <=, >=
Equality: ==, !=
Logical: and, or, not, xor, implies, and the eager pair & and |
These work the way you expect from engineering math. Arithmetic operators produce numeric results. Relational and equality operators produce Boolean results. Logical operators combine booleans.
Here is an expression that checks whether a temperature is within brewing range:
waterTemp >= 90.0 and waterTemp <= 96.0
And one that computes a total cycle time:
grindSeconds + heatSeconds + extractSeconds
Both are just expressions – sequences of values and operators. They become useful when you put them inside calculations and constraints.
and or &? They are not the same
There are two spellings of logical conjunction, and the difference is the one you know from programming languages even though it is spelled differently here.
and is conditional. In the grammar its right-hand side is a reference to
an expression rather than the expression itself, which is what lets it be left
unevaluated when the left side already decides the answer. & is eager: both
sides are plain operands and both are evaluated. or and | divide the same way.
That matters when the right-hand side is expensive or unsafe:
constraint def SafeToBrew {
doc /* The second test is only reached when a tank is present. */
waterTank != null and waterTank.level > minimumLevel
}
With and, the level is never read when there is no tank. With & it would be.
Prefer and and or – they are the safer default and what the rest of this
book uses. Reach for & or | only when you actually want both sides evaluated
regardless.
A tool wrinkle.
&and|are accepted inside a model file, but the standalone expression evaluator rejects them:sysml eval 'true & false'reports that it cannot parse the expression, whilesysml eval 'true and false'answersfalse. If you are testing an expression at the command line, use the word forms.
xor is exclusive or, and implies is logical implication – a implies b is
false only when a is true and b is false. Both are ordinary infix operators,
and both evaluate: sysml eval 'true xor false' gives true, and
sysml eval 'true implies false' gives false.
Operator Precedence and Parentheses
SysML v2 follows standard mathematical precedence: multiplication and division bind tighter than addition and subtraction, relational operators bind tighter than logical ones. But relying on precedence in complex expressions is a recipe for review comments.
Compare these two:
not brewCommand or cupDetected and lidClosed
(not brewCommand) or (cupDetected and lidClosed)
They mean the same thing, but only the second one is obvious at a glance. Parentheses cost nothing to write and save time in every review. Use them whenever an expression has more than one logical operator.
Operators Beyond the Basics
The four families above cover most everyday model logic. The language has more, and you need them once your model gets serious.
Power and modulo. ** (or ^) raises to a power, % is integer remainder.
attribute storedEnergy = boilerVolume * thermalCapacity * tempDelta ** 2;
attribute selfTestSlot = cycleNumber % 10; // every tenth cycle is a self-test
More logical operators. Beyond and, or, not, the spec gives you xor (exclusive or) and implies (logical implication). They are useful when a boolean condition reads more naturally as one of these forms:
attribute brewOk = brewRequested implies cupPresent; // identical to: not brewRequested or cupPresent
attribute exactlyOne = manualOverride xor scheduledRun; // exactly one is active at a time
Null coalescing. ?? returns its left operand if that operand has a value, otherwise its right operand. Use it for defaulting:
configuredTargetTemp ?? 93.0
If configuredTargetTemp is set, you get that value; otherwise you get 93.0. This is far cleaner than if configuredTargetTemp != null ? configuredTargetTemp else 93.0.
Type tests and coercion. istype, hastype, and as ask and answer questions about an element’s classification:
attribute isExactly = sensor istype Thermistor; // is `sensor` exactly Thermistor?
attribute isAKind = sensor hastype Sensor; // is `sensor` Sensor or any subtype?
attribute coerced = sensor as Thermistor; // treat sensor as a Thermistor
istype is exact; hastype includes subtypes; as is a coercion that gives you access to the more specific feature set.
Sequence operators. When an attribute has multiplicity greater than one, you have a sequence, and you operate on it with -> operators. The per-element rule goes in a body expression – braces holding an in parameter, a ;, and the expression over that parameter:
attribute highPower = brewers->select {in b; b.power > 1000.0}; // filter
attribute allTargets = brewers->collect {in b; b.targetTemp}; // map
attribute healthy = brewers->reject {in b; b.fault}; // filter (negated)
attribute allBelow = brewers->forAll {in b; b.targetTemp <= 100.0}; // every element satisfies
attribute anyFault = brewers->exists {in b; b.fault}; // some element satisfies
These compose. A common pattern is “is every brewer’s temperature in the safe range?”:
brewers->forAll {in b; b.waterTemp >= 90.0 and b.waterTemp <= 96.0}
You will reach for ->forAll and ->select constantly once a model has any non-trivial structure. They turn what would be a repeated constraint into one rule that scales with the part count.
Indexing and ranges. #(n) selects the n’th element of a sequence; .. builds a range:
attribute firstBrewer = brewers#(1); // first brewer
attribute cycleNumbers = 1..5; // sequence 1, 2, 3, 4, 5
Metadata access. @ reads a metadata annotation; @@ reads all annotations of a kind. You will see these in Chapter 13, where metadata drives view rendering.
Qualified enumeration references. When two enumerations share a literal name, qualify the reference with :::
mode == BrewMode::Espresso // not just `Espresso`
Most of these operators show up rarely, but when you need ?? or ->forAll, no other operator does the job. Reach for them by name; do not work around them.
String Literals and Structured Values
So far every value in this chapter has been a number or a Boolean. SysML v2 treats strings as ordinary value types too, and it lets you build your own structured value types out of them.
String literals are written with double quotes:
attribute label : String := "Brewing";
attribute statusText : String := if waterLow ? "Low Water" else "System OK";
The quotes are not part of the value. You can include escaped characters like \" (an embedded quote) or \n (a newline) inside the literal. Strings are useful for human-readable labels and diagnostics; the next chapter uses them inside doc comments on requirements.
The second example uses the conditional expression from the next section – if <cond> ? <then> else <else> – with two string results.
Structured attribute definitions group a small, fixed set of named, typed fields into one value. There is no anonymous tuple type in the language – you name the record with an attribute def and then use it like any other type:
attribute def SensorReading {
attribute name : String;
attribute value : Real;
attribute unit : String;
}
attribute reading : SensorReading;
SensorReading is the type of a three-field record – a label, a measurement, and a unit. An attribute def is the right shape when a few values belong together (a sensor reading with its units, a coordinate’s x/y/z) but you do not need a full part def to give them an identity: an attribute value has no occurrence, no ports, and no lifecycle. Compare it to a sequence, which holds many values of one type: a structured attribute holds few values of different types, named.
The Conditional Expression
SysML v2 has a conditional expression with the syntax if <condition> ? <then-value> else <else-value>. This is not an if-statement – it is an expression that evaluates to one of two values.
if ambient < 18.0 ? 94.0 else 93.0
This reads: if the ambient temperature is below 18 degrees, the result is 94.0; otherwise 93.0. You will use this pattern in calculations where the output depends on a condition.
The ? separating the condition from the then-value is required. It prevents ambiguity in cases where the condition itself contains operators.
Calculations: Reusable Computations
A calc def defines a reusable computation. It takes typed inputs, declares a return, and contains an expression that computes the result.
calc def TargetBrewTemp {
in ambient : Real;
return result : Real;
result = if ambient < 18.0 ? 94.0 else 93.0
}
This says: given an ambient temperature, compute a target brew temperature. If the environment is cold (below 18 degrees), aim higher to compensate for heat loss.
A calc def is like a function in a programming language, but it lives inside your model. It has a name, typed parameters, and a body that computes a return value. The in keyword marks input parameters. The return keyword marks the output.
You can use a calculation anywhere you need its result. Inside a part, you create a usage of the calculation:
part def Brewer {
attribute waterTemp : Real;
attribute ambientTemp : Real;
calc targetTemp : TargetBrewTemp {
in ambient = ambientTemp;
return result : Real;
}
}
The usage targetTemp binds the calculation’s input to a concrete attribute. Whenever ambientTemp has a value, targetTemp can be evaluated. This is how calculations connect abstract formulas to specific parts of the model.
A calculation’s body is not restricted to its own input parameters. The body is a normal SysML expression and can refer to any element reachable through the owner chain at the call site. So targetTemp could read brewer.boiler.power directly, without that attribute being passed in as an in parameter – the name resolves at evaluation time, against the calc usage’s surrounding scope.
Why this matters. Authors often over-parameterise calc defs because they assume each call is a sealed black box. It is not: calc bodies resolve names through the owner chain when they evaluate, so a calc that reads
brewer.boiler.powercontinues to work after the boiler’spoweris set, overridden, or computed by another calc. You can compose calcs into a small DAG of named equations without threading every value throughinparameters.
Computed Attributes
For one-shot values that do not need a name or reuse, SysML v2 lets you put the expression directly on an attribute:
part def Brewer {
attribute ambientTemp : Real;
attribute targetTemp : Real = if ambientTemp < 18.0 ? 94.0 else 93.0;
}
targetTemp is now an attribute whose value is computed from ambientTemp. There is no separate calc def and no usage to bind. Reading targetTemp evaluates the right-hand side; if ambientTemp is set, you get a number.
Use a computed attribute when:
- the formula appears in exactly one place,
- it has no parameters (it reads other attributes by name),
- you do not need to reuse it,
- you do not need a named contract to refer to.
Use a calc def instead when any of those things are false. A calc def is a named, parametric, reusable equation. A computed attribute is a one-shot derived value.
Computed attributes evaluate lazily on read, and they compose. You can write:
part def Brewer {
attribute ambientTemp : Real;
attribute targetTemp : Real = if ambientTemp < 18.0 ? 94.0 else 93.0;
attribute boilerLimit : Real = 100.0;
attribute headroom : Real = boilerLimit - targetTemp;
attribute safe : Boolean = headroom > 0.0;
}
Reading safe evaluates headroom, which in turn reads targetTemp, which reads ambientTemp. The evaluation order is dictated by what the reader needs; you do not write it explicitly.
Why this matters. The chapter used to suggest you wrap every conditional default in a
calc def. That is overkill for one-shot fields. Computed attributes give you a small DAG of derived values withoutin/returnceremony, and the lazy-on-read evaluation means you do not pay for them until something asks.
Equations and Overrides
Once you start treating calc defs as named equations, two things follow.
First, the body of a calc def is the equation. Every usage of that calc def is an instance of the same equation, evaluated against whatever scope the usage is in. Two parts that use TargetBrewTemp are not running two different formulas – they are running the same formula with different bindings.
Second, you can replace the bindings without touching the equation. The simplest case is at the usage site:
part highAltitudeBrewer : Brewer {
calc targetTemp : TargetBrewTemp {
in ambient = ambientTemp - 5.0; // override: simulate cooler effective ambient
return result : Real;
}
}
The equation is unchanged; only the input binding moved. The same idea extends to overriding attribute values along a feature chain (boiler.power = 1500.0) or substituting a whole calc def via subclassing and :>>-redefinition. Trade studies, sensitivity sweeps, and “what if the heater were 1.5 kW” exploration all work this way.
Why this matters. Most useful analyses in a system model are not “evaluate the model once”. They are “evaluate it many times with different parameters and compare”. Once you separate equations (the calc bodies) from bindings (the values flowing in), you can vary the bindings without forking the model. Authors who do not see this distinction tend to copy entire packages just to change one number.
Calculations Over Sequences
Once a part has multiplicity, calcs over sequences become the natural way to ask system-level questions. The sequence operators from earlier (->select, ->collect, ->forAll) are first-class inside calc bodies:
calc def WorstHeadroom {
in stations : Brewer[*];
return : Real = stations->collect {in s; s.boilerLimit - s.targetTemp}->reduce min;
}
This calc takes a sequence of brewers and returns the smallest headroom across them all. The ->collect builds a sequence of headroom values; ->reduce min folds that sequence to a single number by applying min pairwise. (min and max in the standard library are two-argument functions, so a sequence is reduced with them rather than aggregated by them.) The whole thing is one expression.
->forAll and ->exists are useful in constraint bodies, not just calcs:
constraint def AllStationsSafe {
in stations : Brewer[*];
stations->forAll {in s; s.waterTemp >= 90.0 and s.waterTemp <= 96.0}
}
This says “every brewer is in the safe range”. One named constraint scales with however many brewers exist.
Why this matters. Without sequence operators in calc and constraint bodies, an author writes parallel attribute trees (“worstHeadroom1, worstHeadroom2, …”) or tries to extract aggregations into the structural model. The collection operators put the aggregation where it belongs – in the equation – and let the system grow without rewriting the calc.
Calculations as ODE Right-Hand Sides
When a calc def captures the time-derivative of a state variable, you can flag that intent by subtyping GetDerivative from the standard library’s StateSpaceRepresentation package:
import StateSpaceRepresentation::*;
calc def HeaterRhs :> GetDerivative {
in t : Real;
in temp : Real;
in heaterPower : Real;
in lossCoeff : Real;
in thermalMass : Real;
return dT_dt : Real = (heaterPower - lossCoeff * temp) / thermalMass;
}
HeaterRhs is still an ordinary calc def. The supertype :> GetDerivative carries the contract: this calc returns the time-derivative of one named state variable. There is no special metadata, no magic string, no tool-specific annotation – just a stdlib supertype.
A second pattern, GetOutput, captures algebraic outputs as a function of state and inputs (for example, “the sensor reading is the boiler’s temp plus a fixed offset”). Together, GetDerivative and GetOutput describe a state-space block in the spec’s standard way; they are usually grouped under an action def :> ContinuousStateSpaceDynamics. All three come from StateSpaceRepresentation.
We cover ODE authoring properly in Chapter 16, which shows the coffee machine evolving over time with a heater turning on and off. For now, the only thing to take away is that the spec gives you a language shape for derivatives and algebraic outputs – you do not need to leave SysML to describe them.
Why this matters. Older sysml-rs material taught ODE authoring through
@ToolVariable { derivative = "..." }metadata. That pattern is removed. The spec-standard pattern iscalc def Foo :> GetDerivative { ... }, and the runtime picks up the contract from the supertype. As a SysML author, the change is: write a calc def, type its body the way you would type any equation, give it the right supertype, and the rest is the runtime’s problem.
Constraints: Reusable Rules
A constraint def defines a reusable boolean rule. Its body is an expression that must evaluate to true for the model to be considered valid.
constraint def BrewTempInRange {
in temp : Real;
temp >= 90.0 and temp <= 96.0
}
This constraint takes a temperature and checks that it falls within the safe brewing range. It does not enforce anything on its own – it just defines the rule. Think of it as a named predicate: a question that can be answered true or false.
Constraints and calculations look similar. The difference is intent:
- A
calc defcomputes a value. Its body is an assignment:result = <expression>. - A
constraint defchecks a condition. Its body is a boolean expression with no assignment.
Both are reusable. Both take typed parameters. But a calculation answers “what is the value?” while a constraint answers “is this acceptable?”
How Constraints Find Their Values
Constraint bodies often reference attributes by short name – waterTemp, cupPresent – and you might wonder how the language decides which waterTemp. Two scoping rules cover the cases you will hit:
-
In a part context. A constraint asserted inside a part resolves names by walking the owner chain outward.
waterTempfirst looks for an attribute namedwaterTempon the part the assertion lives in, then on its owner, and so on. The first match wins. -
In a requirement context. A constraint inside a
requirement defresolves names against the requirement’s declaredsubject,actor, and parameter list. The satisfy site (covered in Chapter 10) supplies the concrete bindings.
Mixing the two scoping models is the single biggest source of “constraint always passes” or “constraint cannot find variable” failures in real models. If you assert a constraint in a part and the body references specification.inputSupply.voltage, every name on that path must resolve through the owner chain – there is no implicit “specification” sitting at the top level of the package. If the model intended specification to be a parameter, it belongs as an in parameter on the constraint or as a parameter on the surrounding requirement.
A short rule of thumb: constraints inside parts read attributes by name from the enclosing part. Constraints inside requirements read attributes by name from the requirement’s parameters and subject. If you find yourself reaching across packages to name a value, you probably want a parameter.
Asserting Constraints
A constraint definition by itself is just a template. To apply it, you use assert constraint inside a part or other context:
part def Brewer {
attribute waterTemp : Real;
attribute ambientTemp : Real;
calc targetTemp : TargetBrewTemp {
in ambient = ambientTemp;
return result : Real;
}
assert constraint tempCheck : BrewTempInRange {
in temp = waterTemp;
}
}
The assert constraint line does two things. First, it creates a usage of BrewTempInRange inside Brewer, binding the constraint’s temp parameter to the brewer’s waterTemp attribute. Second, the assert keyword declares that this constraint must hold – it is not just a check you could run, it is a stated requirement on this part.
An asserted constraint is a commitment. It says: in any valid instance of Brewer, waterTemp must be between 90.0 and 96.0. When the constraint is evaluated – either by a static check or as the model runs – it produces a verdict that is either true (the assertion holds) or false (it does not), with the operand values that contributed. The same machinery powers require constraint inside a requirement; we pick that up in Chapter 10.
You can also assert a constraint inline, without referencing a named definition:
assert constraint {
waterTemp >= 90.0 and waterTemp <= 96.0
}
This works for one-off checks, but named constraints are better for anything you might reuse or need to trace to a requirement.
Asserting That Something Must Not Hold
The opposite of assert constraint is assert not constraint. It says: this combination must never hold.
part def Brewer {
attribute dispenseRequested : Boolean;
attribute cupPresent : Boolean;
assert not constraint {
dispenseRequested and not cupPresent
}
}
This reads cleanly as “it must never be the case that a dispense was requested with no cup present”. You could write the same rule as a positive assertion – assert constraint { not dispenseRequested or cupPresent } – but the negated form usually reads better when the unsafe combination is what you care about.
assert not is a separate language form, not just a textual transformation. Use it whenever the rule you want to express is “this must never be true.”
Building the Coffee Machine’s Logic
The coffee machine from previous chapters has structure, connections, actions, and states. Now add the quantitative logic that makes it checkable.
The Temperature Calculation
The brewer needs to know what temperature to target. That depends on ambient conditions – a cold room means more heat loss, so the target should be higher. This is exactly what calc def is for:
package CoffeeMachineAnalysis {
import ScalarValues::*;
import CoffeeMachineDomain::*;
calc def TargetBrewTemp {
in ambient : Real;
return target : Real;
target = if ambient < 18.0 ? 94.0 else 93.0
}
}
This is a simple two-branch formula. In a real system, it might be a polynomial curve fit or a lookup table. The point is that it lives in one place with a clear name, typed inputs, and a typed output.
The Safety Interlock
A coffee machine should not dispense hot water unless a cup is present. This is a safety interlock – a boolean rule that must be satisfied before the brew action proceeds:
package CoffeeMachineAnalysis {
import ScalarValues::*;
import CoffeeMachineDomain::*;
calc def TargetBrewTemp {
in ambient : Real;
return target : Real;
target = if ambient < 18.0 ? 94.0 else 93.0
}
constraint def SafetyInterlock {
in cupDetected : Boolean;
in brewCommand : Boolean;
not brewCommand or cupDetected
}
}
Read the constraint body carefully: not brewCommand or cupDetected. This is the logical implication “if brewCommand, then cupDetected.” It evaluates to true in three cases: no brew command was given, a cup is detected, or both. It is false only when a brew is commanded with no cup present – exactly the unsafe condition.
You could write the same rule using implies:
constraint def SafetyInterlock {
in cupDetected : Boolean;
in brewCommand : Boolean;
brewCommand implies cupDetected
}
Both forms mean the same thing. implies reads more naturally when the rule is “A requires B”; not A or B reads more naturally when you want the user to see both branches.
Applying Both to the Machine
Now bring the calculation and constraint into the coffee machine structure:
package CoffeeMachineSystem {
import ScalarValues::*;
import CoffeeMachineDomain::*;
import CoffeeMachineAnalysis::*;
part coffeeMachine : CoffeeMachine {
attribute ambientTemp : Real;
attribute cupPresent : Boolean;
attribute brewRequested : Boolean;
part brewer : Brewer {
calc targetTemp : TargetBrewTemp {
in ambient = coffeeMachine.ambientTemp;
return target : Real;
}
assert constraint tempSafe : BrewTempInRange {
in temp = waterTemp;
}
}
assert constraint interlock : SafetyInterlock {
in cupDetected = cupPresent;
in brewCommand = brewRequested;
}
}
}
Two things are asserted here. Inside brewer, the tempSafe constraint checks that the actual water temperature stays within range. At the machine level, the interlock constraint checks that no brew can happen without a cup. Both are named, both are traceable, and both can be verified independently.
The targetTemp calculation inside brewer binds to the machine’s ambient temperature. It does not enforce anything – it computes the target. The tempSafe constraint checks that the actual temperature (measured by a sensor, represented by waterTemp) stays in range. These are complementary: the calculation says what the temperature should be, and the constraint says what range is acceptable.
Calculations with Multiple Inputs
A calculation can take as many inputs as needed. Here is one that estimates the total brew cycle time:
calc def BrewCycleTime {
in grindSeconds : Real;
in heatSeconds : Real;
in extractSeconds : Real;
return total : Real;
total = grindSeconds + heatSeconds + extractSeconds
}
And a more interesting one that adjusts extraction time based on grind size:
calc def AdjustedExtractTime {
in baseSeconds : Real;
in grindFactor : Real;
return adjusted : Real;
adjusted = baseSeconds * grindFactor
}
You can compose calculations by using one calculation’s output as another’s input. This lets you build up complex formulas from simple, testable pieces.
Constraints with Multiple Conditions
A single constraint can check several conditions at once using logical operators:
constraint def ReadyToBrew {
in waterLevel : Real;
in minWaterLevel : Real;
in temp : Real;
in cupPresent : Boolean;
cupPresent and waterLevel >= minWaterLevel
and temp >= 90.0 and temp <= 96.0
}
This combines three checks: cup is present, water level is sufficient, and temperature is in range. It is a single named rule that answers a clear engineering question: is the machine ready to brew?
When a compound constraint like this gets long, consider splitting it. You already have BrewTempInRange and SafetyInterlock as separate constraints. A composite constraint can reference them conceptually while keeping the logic explicit.
When to Use What
Expressions, calculations, and constraints serve different purposes. Here is when to reach for each:
Use a bare expression when you need a one-time value – an attribute default, a transition guard, or a simple formula that appears in exactly one place.
Use a computed attribute when the value is derived from other attributes in the same part, and you do not need parameters or a name to refer to it elsewhere.
Use calc def when a computation is reusable, has clear inputs and outputs, or is complex enough to deserve a name. If you find yourself writing the same arithmetic in two places, extract it into a calculation.
Use constraint def when a boolean rule represents an engineering requirement – a range check, a safety condition, or an interlock. If someone might ask “where is that rule defined?” it should be a named constraint.
Use assert constraint to commit to a rule in a specific context. An unasserted constraint is just a definition sitting on a shelf. Assertion is what makes it binding.
Use assert not constraint when the rule you want is “this combination must never happen.” The negated form often reads better than its positive equivalent.
Reading Constraints as Math
Every expression you write in SysML v2 has a clean mathematical reading. Reviewers and stakeholders often see the math form before they see the source. Knowing the operator-to-notation correspondence makes constraints easier to read in both directions:
| Source | Math notation |
|---|---|
>=, <=, != | ≥, ≤, ≠ |
and, or, not | ∧, ∨, ¬ |
implies | ⇒ |
^ or ** | superscript: xⁿ |
/ | fraction: a / b |
abs(x) | ` |
sqrt(x) | √x |
brewer.boiler.power | subscripted identifier |
So temp >= 90.0 and temp <= 96.0 reads as 90.0 ≤ temp ≤ 96.0, and not brewCommand or cupDetected reads as ¬brewCommand ∨ cupDetected. When a constraint is hard to read in source form, try writing it the way it would render mathematically – often the cleaner phrasing is the one that translates directly. Prefer abs(x) over an ad-hoc conditional, prefer implies over not A or B when the rule is “A requires B”, and prefer pow(x, 2) over x ** 2 when the constraint is going to be reviewed verbally.
Common Mistakes
Writing constraints that do not evaluate to boolean. A constraint body must be an expression that produces true or false. If you accidentally write an arithmetic expression (like a + b) instead of a comparison (like a + b <= limit), the model will not check.
Forgetting to assert. Defining a constraint def does nothing until you assert it somewhere. If you define SafetyInterlock but never write assert constraint ... : SafetyInterlock, the rule exists in the model but is never checked. This is the most common oversight when adding constraints.
Duplicating logic instead of naming it. If the expression waterTemp >= 90.0 and waterTemp <= 96.0 appears in three places, a change to the range requires three edits. Define it once as a constraint def and reference it wherever needed.
Using calculations where constraints belong. If you write a calc def that returns a boolean, ask yourself whether it should be a constraint def instead. Calculations compute values; constraints check conditions. Using the right one makes the model’s intent clearer to reviewers.
Referencing an attribute that has no assigned value. When a constraint walks a chain like specification.inputSupply.busbarCurrentRating, every step must resolve to a bound value. If specification is declared but never given a binding (no part instance, no :>> redefinition, no satisfy site supplying it), the chain cannot evaluate. The fix is almost always to make the missing binding explicit – either declare the constraint inside a part that owns those features, or add the missing values as in parameters and let the satisfy site supply them. The error you see at evaluation time will name the unbound variable; treat it as a model-completeness signal, not a bug in the constraint.
What Comes Next
The coffee machine now has checkable logic: a temperature calculation that adapts to ambient conditions, a safety interlock that prevents dispensing without a cup, and the language vocabulary – expanded operators, equations, sequence calculations, computed attributes, and asserted constraints both positive and negated – to write the rest of the system.
In Chapter 10, you will turn these engineering rules into formal requirements and trace them from stakeholder intent through design to verification evidence. The constraints you defined here will become the “satisfy” targets that link requirements to the model.
In Chapter 16, you will let the same model run – ODE state variables advancing in time, calc defs serving as right-hand sides, asserted constraints firing per-tick as live monitors. The :> GetDerivative pattern from earlier in this chapter is the doorway into that.
Requirements and Verification
Your coffee machine has structure, connections, behavior, states, and constraints. You can describe what the system is made of, how it behaves, and what rules it must obey. But none of that answers a fundamental engineering question: does the design actually meet what was asked for?
That is what requirements and verification are for. A requirement captures something the system must do or be. A satisfaction link says which part of the design addresses it. A verification case says how you plan to prove it. Together, these three elements form a traceability loop – the mechanism that connects stakeholder intent to design decisions to objective evidence.
SysML v2 models requirements as first-class elements, not as external documents referenced by ID. They live in the same model as the parts, actions, and constraints they trace to. This means tools can check whether every requirement has a design link and a verification plan, rather than relying on spreadsheets and manual audits.
Defining a Requirement
A requirement starts with requirement def. Like part definitions and action definitions, a requirement definition is a reusable type. You can give it a short identifier (useful for document references) and a doc comment that carries the requirement text.
The short identifier goes in angle brackets and single quotes – <'REQ-TEMP-001'>. The quotes are what let it contain hyphens, which a plain identifier cannot; Appendix H covers that form and the rest of the naming rules.
package CoffeeMachineRequirements {
requirement def <'REQ-TEMP-001'> BrewTempReq {
doc /* The brewer extraction temperature shall remain
between 90 and 96 degrees Celsius during brewing. */
}
}
BrewTempReq is the model name you use in SysML code. REQ-TEMP-001 is the short ID – a human-readable tag that maps to whatever requirement numbering scheme your project uses. The doc comment holds the requirement text itself. This text is not checked by the model, but it is visible to reviewers and tools.
You can define as many requirements as your project needs. Here is a second one for the safety interlock from Chapter 9:
package CoffeeMachineRequirements {
requirement def <'REQ-TEMP-001'> BrewTempReq {
doc /* The brewer extraction temperature shall remain
between 90 and 96 degrees Celsius during brewing. */
}
requirement def <'REQ-SAFE-001'> SafetyInterlockReq {
doc /* The system shall not initiate a brew cycle
unless a cup is detected on the drip tray. */
}
}
Each requirement stands alone as a definition. It does not yet say anything about which part of the design addresses it or how you would test it. Those connections come next.
Requirement Text vs. Formal Constraints
The doc comment on a requirement is prose. It communicates intent to human readers, but the model checker cannot evaluate it. For requirements where precision matters, you can add a formal constraint inside the requirement body.
requirement def <'REQ-TEMP-001'> BrewTempReq {
doc /* The brewer extraction temperature shall remain
between 90 and 96 degrees Celsius during brewing. */
attribute temp : Real;
require constraint { temp >= 90.0 and temp <= 96.0; }
}
The require constraint block adds a machine-checkable condition alongside the prose. The temp attribute is a parameter of the requirement – it represents whatever value needs to satisfy the condition. When this requirement is linked to a design element, the attribute can be bound to an actual property.
A formal constraint also turns the requirement into something the runtime can produce evidence for. During constraint evaluation, verification, or simulation-coupled verification, the constraint runs against the bound values and produces a Pass / Fail / Inconclusive / Error verdict (covered in How Verification Verdicts Work, below).
Not every requirement needs a formal constraint. Some requirements are inherently qualitative (“the user manual shall be written in English”) and do not benefit from formalization. The general rule: if the requirement text contains a number, a range, or a logical condition, consider adding a require constraint to make it checkable.
Subjects, Actors, and the Binding Surface
A requirement can declare a subject – the element it constrains. The subject says “this requirement is about that thing.”
requirement def <'REQ-TEMP-001'> BrewTempReq {
doc /* The brewer extraction temperature shall remain
between 90 and 96 degrees Celsius during brewing. */
subject brewer : Brewer;
}
The subject keyword declares that BrewTempReq applies to a Brewer. This is a typed parameter: any element that satisfies this requirement must be (or contain) a Brewer. Subjects make requirements more precise. Without a subject, a requirement floats – you know it exists, but not what it applies to. With a subject, the requirement is anchored to a specific kind of element.
A requirement can also declare one or more actors – the elements that interact with the subject. Actors are typed in the same way as subjects:
requirement def <'REQ-SAFE-001'> SafetyInterlockReq {
doc /* The system shall not initiate a brew cycle
unless a cup is detected on the drip tray. */
subject machine : CoffeeMachine;
actor user : Operator;
}
The actor declaration says “this requirement is about how the Operator interacts with the CoffeeMachine.” Actors are useful when a requirement is fundamentally about an interaction, not just a property of a single part.
Together, the subject, the actors, and any attribute parameters form the requirement’s binding surface – the set of names that a satisfy site must supply. The binding surface is also the scope inside which a require constraint resolves names (recall Chapter 9, “How Constraints Find Their Values”). When the runtime evaluates the constraint, it walks this surface, not the surrounding package, to find values.
Why this matters. Authors often write
require constraintbodies that reference attributes the requirement does not declare. The constraint then either picks up the wrong attribute by accident or fails to evaluate because no value is bound. The fix is to declare every name the constraint uses as part of the binding surface – subject, actor, orattribute– so the satisfy site has a clean place to supply them.
Satisfying a Requirement
A requirement on its own is just an assertion about what should be true. To close the loop, you need to say which part of the design addresses it. That is what satisfy does.
part def CoffeeMachine {
part brewer : Brewer;
satisfy requirement : BrewTempReq;
}
The satisfy statement inside CoffeeMachine says: this design element claims to address BrewTempReq. The exact strength of that claim depends on the requirement.
Requirements with no formal constraint are pure traceability links. The model does not check whether CoffeeMachine actually meets the qualitative goal – the link just makes the relationship explicit and queryable. A tool or a reviewer can ask “which requirements does CoffeeMachine satisfy?” and get a definitive answer.
Requirements that carry a require constraint are stronger. The satisfy site supplies the bindings for the requirement’s parameters (subject, actors, attributes), and at evaluation time – whether driven by a static check, a verification case, or a simulation – the constraint is evaluated against those bindings and produces a verdict. This is not a manual claim; the model knows whether the constraint holds for the values the satisfy site supplied. We unpack the verdict shape in How Verification Verdicts Work, below.
You can satisfy multiple requirements from the same design element:
part def CoffeeMachine {
part brewer : Brewer;
part grinder : Grinder;
part waterTank : WaterTank;
satisfy requirement : BrewTempReq;
satisfy requirement : SafetyInterlockReq;
}
You can also place satisfaction links at a more specific level. If the temperature requirement is really about the brewer, not the whole machine, put the satisfaction there:
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
satisfy requirement : BrewTempReq;
}
Where you place the satisfy link is a modeling decision. Put it at the level of the design element that is actually responsible for meeting the requirement. If the brewer controls the temperature, the brewer should carry the satisfaction link.
Verification Cases
Satisfaction says “this design addresses this requirement.” But that is a claim, not evidence. Verification cases capture how you plan to produce evidence.
A verification case is defined with verification def. Inside it, an objective block declares which requirement it verifies:
verification def BrewTempTest {
doc /* Measure extraction temperature across 10 consecutive
brew cycles and confirm all readings fall within range. */
objective {
verify requirement : BrewTempReq;
}
}
BrewTempTest is a verification case that targets BrewTempReq. The doc comment describes the test procedure in prose. The verify statement inside the objective block creates the formal link.
A verification case can verify more than one requirement:
verification def SafetyInterlockTest {
doc /* Attempt to start a brew cycle with no cup present.
Confirm the system rejects the command. Then place a cup,
retry, and confirm the cycle starts. */
objective {
verify requirement : SafetyInterlockReq;
}
}
You can also define actions inside a verification case to describe the test procedure as modeled steps:
verification def SafetyInterlockTest {
doc /* Verify that the brew interlock prevents operation
without a cup. */
action removeCup { doc /* Remove any cup from the tray. */ }
action attemptBrew { doc /* Press the brew button. */ }
action confirmRejection { doc /* Verify the machine refuses to brew. */ }
action placeCup { doc /* Place a cup on the tray. */ }
action retryBrew { doc /* Press the brew button again. */ }
action confirmSuccess { doc /* Verify the brew cycle starts. */ }
first removeCup then attemptBrew then confirmRejection
then placeCup then retryBrew then confirmSuccess;
objective {
verify requirement : SafetyInterlockReq;
}
}
This level of detail is optional. For many projects the prose description in doc is sufficient. But when your verification plan will eventually drive automated test execution or when reviewers need to see the exact sequence, modeling the steps as actions makes the procedure unambiguous. The essential thing is the verify link – it connects the requirement to a planned piece of evidence.
How Verification Verdicts Work
sysml-rs extends the spec here. The OMG spec defines
verifyas a relationship that produces an outcome, but does not normatively define the outcome’s shape. sysml-rs settles on a four-valued verdict with a structured payload, described below. Other SysML v2 implementations may produce different verdict shapes; the spec-level traceability still works either way.
When a requirement that carries a require constraint is evaluated – by a verify case, by assert constraint running against the satisfy site, or by a simulation step – the result is a verdict. A verdict is one of four values:
Pass— the constraint evaluated totrueagainst the bound values. The requirement is met for this evaluation.Fail— the constraint evaluated tofalse. The requirement is violated.Inconclusive— the constraint did not have enough information to decide (for example, an attribute on the binding surface had no assigned value, or the evaluation depended on a not-yet-simulated time point).Error— the evaluation itself broke (a name did not resolve, a type mismatch, division by zero). The model has a defect that must be fixed before the verdict means anything.
A verdict carries more than just the kind. The structure typically looks like this:
Verdict {
kind: Pass | Fail | Inconclusive | Error
actual: <value the constraint evaluated to>
expected: <value the constraint required>
margin: <how close to the boundary, when applicable>
message: <human-readable explanation>
evidence: <reference to the source: a tick number, a test step, etc.>
}
For BrewTempReq with the constraint temp >= 90.0 and temp <= 96.0, three sample verdicts might look like:
Pass: actual=92.4, expected=[90.0, 96.0], margin=2.4
Fail: actual=98.1, expected=[90.0, 96.0], margin=-2.1
Inconclusive: actual=null, message="`temp` had no assigned value"
Error: message="undefined variable: `specification.temperature`"
The Inconclusive case is the one that surprises authors. A passing verdict is good news; a failing verdict tells you something concrete to fix; an Error is a model bug; but Inconclusive says the model is incomplete – the constraint is well-formed and the design site is willing to be checked, there just is not yet a value to check against. In a static review, that means the satisfy site needs to bind a missing parameter. In a simulation, it usually means the constraint is being checked before the relevant state variable has been initialised.
Treat each kind as actionable:
Pass→ record evidence; you have met the requirement.Fail→ fix the design or relax the requirement (with stakeholder agreement).Inconclusive→ bind the missing values; complete the model.Error→ fix the model; the constraint is unevaluable as-written.
Verifying Against a Simulation
sysml-rs extends the spec here. Coupling a
verifyto a running simulation – so the verdict is computed from time-series state, not from static attribute values – is one of sysml-rs’s intended use cases. The OMG spec is more abstract about how verification produces its outcome.
For requirements that constrain behaviour over time (a temperature stays in range, a cycle completes within a deadline), the natural evidence is a simulation run. Here is what that looks like end-to-end for the coffee machine.
In Chapter 9 you saw the :> GetDerivative pattern for ODE right-hand sides. The brewer’s heater, modelled as a state with an ODE, looks like:
import StateSpaceRepresentation::*;
calc def HeaterRhs :> GetDerivative {
in t : Real;
in temp : Real;
in heaterPower : Real;
in lossCoeff : Real;
in thermalMass : Real;
return dT_dt : Real = (heaterPower - lossCoeff * temp) / thermalMass;
}
The brewer holds the state variable waterTemp and uses HeaterRhs to advance it. Now we tie verification to that:
package CoffeeMachineVerification {
import CoffeeMachineDomain::*;
import CoffeeMachineRequirements::*;
verification def BrewTempSimulationTest {
doc /* Simulate one brew cycle. The brewer's waterTemp must
stay within [90.0, 96.0] across the entire extraction
window. */
subject brewer : Brewer;
objective {
verify requirement BrewTempReq {
in temp = brewer.waterTemp;
}
}
}
}
The verify requirement block binds the requirement’s temp parameter to the simulated brewer.waterTemp. When the verification runs against a simulation:
- The simulation advances
waterTempover time usingHeaterRhs. - At each tick, the
require constraintis evaluated against the currentwaterTempvalue. - If
waterTempever leaves[90.0, 96.0], the verdict isFailand the evidence carries the offending tick. - If
waterTempstays in range across the whole simulated window, the verdict isPass. - If the simulation never starts heating (a stuck binding, a missing input), the verdict is
Inconclusivefor that window.
The shape that makes this work is the same shape as the rest of the chapter: a requirement with a typed binding surface, a require constraint body that resolves names against that surface, and a verify link that supplies the bindings – only this time the bindings come from running state, not from a static attribute.
Why this matters. A numerical requirement (a temperature, a duration, a tolerance) is rarely satisfiable by inspection. Coupling
verifyto a simulation gives you primary evidence: the constraint either held over the simulated trajectory or it did not, and you can replay the failing tick. The chapter’s earlier verification cases describe what you would do; this is what you can run.
The Full Traceability Loop
With requirements, satisfaction, and verification in place, you have a complete traceability loop. Here it is for the coffee machine, all in one package:
package CoffeeMachineRequirements {
import ScalarValues::*;
import CoffeeMachineDomain::*;
// --- Requirements ---
requirement def <'REQ-TEMP-001'> BrewTempReq {
doc /* The brewer extraction temperature shall remain
between 90 and 96 degrees Celsius during brewing. */
subject brewer : Brewer;
attribute temp : Real;
require constraint { temp >= 90.0 and temp <= 96.0; }
}
requirement def <'REQ-SAFE-001'> SafetyInterlockReq {
doc /* The system shall not initiate a brew cycle
unless a cup is detected on the drip tray. */
subject machine : CoffeeMachine;
}
requirement def <'REQ-BREW-001'> BrewTimeReq {
doc /* A brew cycle shall complete within 60 seconds
from initiation to coffee dispensed. */
subject machine : CoffeeMachine;
attribute duration : Real;
require constraint { duration <= 60.0; }
}
// --- Satisfaction (in a usage context) ---
part coffeeMachine : CoffeeMachine {
satisfy requirement : BrewTempReq;
satisfy requirement : SafetyInterlockReq;
satisfy requirement : BrewTimeReq;
}
// The form above is the most common, but the spec also accepts:
//
// satisfy requirement brewTempReq : BrewTempReq by brewer;
//
// which gives the satisfaction usage its own name (`brewTempReq`)
// and lets you spell out which design element does the satisfying
// (`by brewer`) when it is not the enclosing element.
// --- Verification ---
verification def BrewTempTest {
doc /* Measure extraction temperature across 10 consecutive
brew cycles. All readings must fall between 90 and
96 degrees Celsius. */
objective {
verify requirement : BrewTempReq;
}
}
verification def SafetyInterlockTest {
doc /* Attempt brew with no cup: confirm rejection.
Place cup, retry: confirm cycle starts. */
objective {
verify requirement : SafetyInterlockReq;
}
}
verification def BrewTimeTest {
doc /* Time 5 consecutive brew cycles from button press
to coffee dispensed. All must complete within
60 seconds. */
objective {
verify requirement : BrewTimeReq;
}
}
}
Read this from the bottom up: BrewTempTest verifies BrewTempReq, which is satisfied by coffeeMachine. That is one complete loop. Every requirement has a design link (who addresses it) and an evidence link (how you check it). A reviewer can trace from any requirement to both its design rationale and its verification plan.
This is where SysML v2 earns its keep as an engineering tool. The model is not just a picture of the system – it is a queryable database of commitments. You can ask:
- Which requirements have no satisfaction link? (Unaddressed requirements.)
- Which requirements have no verification case? (Unverified claims.)
- Which verification cases are not linked to any requirement? (Orphan tests.)
These are the questions that project reviews are built around. With model-based requirements, the answers come from the model itself rather than from manually maintained traceability matrices.
Requirement Decomposition
Large requirements are hard to verify directly. A common pattern is to decompose a top-level requirement into smaller, testable pieces. You can express this using nested requirements or by specializing a parent requirement.
requirement def <'REQ-PERF-001'> PerformanceReq {
doc /* The coffee machine shall deliver acceptable
performance across all operating conditions. */
requirement <'REQ-PERF-001a'> startupTimeReq {
doc /* The machine shall reach operating temperature
within 90 seconds of power-on. */
}
requirement <'REQ-PERF-001b'> brewTimeReq {
doc /* Each brew cycle shall complete within
60 seconds of initiation. */
}
}
The nested requirements startupTimeReq and brewTimeReq are children of PerformanceReq. Each child can have its own satisfaction and verification links. The parent captures the high-level intent; the children are the testable leaves.
Keep decomposition shallow. One or two levels is usually enough. Deep requirement hierarchies create more traceability overhead than they solve.
Use Cases and Concerns
SysML v2 includes two related concepts that provide context for requirements.
A use case describes a goal-oriented interaction with the system. It is defined with use case def:
use case def BrewEspresso {
doc /* The user selects espresso, the machine grinds beans,
heats water, extracts coffee, and dispenses into cup. */
subject machine : CoffeeMachine;
objective {
doc /* Deliver a single espresso shot to the user. */
}
}
Use cases sit at a higher level than actions. Where an action definition describes a sequence of steps, a use case describes a stakeholder goal. Use cases are useful for capturing “why does this behavior exist?” and for organizing requirements around user-facing scenarios.
A concern captures a stakeholder interest or worry:
concern def SafetyConcern {
doc /* The machine must not create burn hazards or
dispense onto the counter without a cup. */
}
Concerns are lightweight. They give requirements a “why” – a named reason for their existence. You do not need concerns for every requirement, but they help when a reviewer asks “why does this requirement exist?” and the answer involves stakeholder context that is not obvious from the requirement text alone.
Both use cases and concerns are optional. Many projects work fine with just requirements, satisfaction, and verification. Add use cases when you need to organize requirements around user goals. Add concerns when stakeholder rationale needs to be explicit.
Analysis Cases
A verification case asks a yes/no question – does the design meet the requirement? An analysis case asks a different question – what is the answer? – and returns a typed result. Use analysis def:
analysis def ThermalLossAnalysis {
doc /* Estimate the heat lost from the brewer during a 30-second
idle period at an ambient temperature of 20 degrees C. */
subject brewer : Brewer;
return lossRate : Real;
}
analysis def defines a case whose purpose is to compute something, not to check something. The body is shaped like the calc def from Chapter 9: it declares inputs (here, via the subject), does work, and returns typed outputs. The difference from a bare calc is that an analysis case carries the same envelope as a verification case – a subject, a doc-driven purpose, optional actor – so it sits inside the same traceability structure as the rest of the model.
Reach for analysis def when the engineering question is estimation (worst-case heat loss, peak load, mean time to brew) rather than compliance. The analysis case can even feed a verification case: it produces the number the verification case then checks against a requirement.
The General case Envelope
Verification, analysis, and use cases are siblings. All three are specializations of a single case def:
case def ThermalLoadProfile {
subject brewer : Brewer;
return idleLoad : Real;
return heatingLoad : Real;
}
case def is the common envelope. Every case definition can carry a subject, an optional actor, an objective, and a body. verification def adds a verify requirement linkage; analysis def adds return values; use case def adds stakeholder actions. The grammar equips them with the same structure; the intent is what you choose when you pick the keyword.
For most modeling, you spell out the specific kind (verification def, analysis def, use case def) and never write the bare case def. But knowing they share an envelope pays off when you build tooling that treats all three uniformly, or when you specialize a case family of your own.
Rolling Up Verdicts Across Cases
The How Verification Verdicts Work section above defined a single verdict’s four values – Pass, Fail, Inconclusive, Error. A requirement is rarely checked once. It is checked against several verification cases, or, in a simulation-coupled verification, at every tick of a run. The individual verdicts are rolled up into one overall judgement for the requirement:
- If every verdict is
Pass, the requirement isPass. - If any verdict is
Fail, the requirement isFail. A single failing observation is enough – the requirement was violated. - If there are no
Failverdicts but at least oneInconclusive, the requirement isInconclusive. You cannot claimPasswhen part of the evidence was undecidable. - If all verdicts are
Error, the requirement isError– the model itself is broken and no verification result is trustworthy until it is fixed.
This rollup is what makes verification metric-able. A project dashboard can report a pass rate per requirement (how many of its checks were Pass), flag requirements with outstanding Inconclusive verdicts, and surface the requirements carrying the most fails. The point of the four-valued verdict – and especially the Inconclusive case – is that absence of a fail is not the same as a pass. Rollup preserves that distinction at the requirement level.
Putting It Together: The Coffee Machine Grows
The coffee machine now has three layers of traceability. Here is how the pieces connect:
Structure (from Chapter 2 through Chapter 6): CoffeeMachine contains Grinder, Brewer, and WaterTank, connected through ports and interfaces.
Behavior (from Chapter 7 and Chapter 8): BrewCycle defines the action sequence. MachineStates defines the operating modes with guarded transitions.
Constraints (from Chapter 9): BrewTempSafe checks the temperature range. SafetyInterlock prevents brewing without a cup.
Requirements and verification (this chapter): BrewTempReq, SafetyInterlockReq, and BrewTimeReq capture the engineering intent. Satisfaction links connect them to the design. Verification cases connect them to evidence.
The constraints from Chapter 9 and the requirements from this chapter serve different purposes. A constraint is a rule the model enforces – it evaluates to true or false against actual values. A requirement is a commitment the project tracks – it may reference a constraint, but its primary purpose is traceability.
Consider the temperature range. In Chapter 9, you defined:
constraint def BrewTempSafe {
in temp : Real;
temp >= 90.0 and temp <= 96.0;
}
In this chapter, you defined:
requirement def <'REQ-TEMP-001'> BrewTempReq {
doc /* The brewer extraction temperature shall remain
between 90 and 96 degrees Celsius during brewing. */
attribute temp : Real;
require constraint { temp >= 90.0 and temp <= 96.0; }
}
These are not redundant. The constraint lives in the design and can be asserted against actual values during analysis or simulation. The requirement lives in the traceability structure and connects to satisfaction links, verification cases, and project reviews. You can have a constraint without a requirement (an internal design rule that nobody asked for) and a requirement without a constraint (a qualitative goal that cannot be formalized). When both exist for the same concern, the constraint makes the requirement checkable and the requirement makes the constraint traceable.
Visualizing Requirements and Traceability
Common Mistakes
Satisfaction without verification. A satisfy link is a claim, not evidence. If you satisfy a requirement but never define a verification case for it, you have an unverified claim. Some tools will flag this, but even without tooling, make it a habit: every satisfy should have a corresponding verify somewhere.
Requirements with no subject. A requirement without a subject can still be satisfied, but it is harder for reviewers to understand what it applies to. When you know which element a requirement targets, say so with subject.
Overly deep decomposition. Splitting one requirement into ten sub-requirements creates a maintenance burden. Each sub-requirement needs its own satisfaction and verification links. Prefer fewer, testable leaf requirements over deep hierarchies.
Duplicating constraint logic in requirements. If you already have a constraint def BrewTempSafe from Chapter 9, you do not need to rewrite that logic inside the requirement. You can reference the constraint or simply note in the requirement text that the constraint captures the formal condition. Do not maintain the same expression in two places.
What You Have Built
Your coffee machine model now includes:
- Three requirements with short IDs, prose text, subjects, and formal constraints
- Satisfaction links from the design to each requirement
- Verification cases with objectives that trace back to each requirement
- A complete traceability loop: requirement to design to evidence
This is the layer that makes the model auditable. In Chapter 11, you will assemble all of these pieces – structure, behavior, constraints, requirements, and verification – into one coherent system model.
Your Complete System Model
Over the past nine chapters, you built a coffee machine model piece by piece: structure, ports, connections, actions, states, constraints, requirements, and verification. Each chapter added one concern. This chapter assembles everything into a single coherent system model.
This chapter does not teach new syntax. Instead, it shows how the pieces fit together – and what a complete, reviewable system model looks like.
The Model So Far
Here is what each chapter contributed:
- Chapter 2 defined the core types:
Grinder,Brewer,WaterTank,CoffeeMachine, plus item definitions forWater,CoffeeBeans, andBrewedCoffee. - Chapter 5 added typed ports to each component:
beanInputandgroundOutputon the grinder;waterInlet,groundInput, andbrewOutputon the brewer;waterOuton the water tank. - Chapter 6 wired the components together with connections and typed item flows.
- Chapter 7 defined
BrewCycleas a sequence of actions with parallel branches. - Chapter 8 defined
MachineStateswith guarded transitions between operating modes. - Chapter 9 added
BrewTempTarget(calculation) andSafetyInterlock(constraint). - Chapter 10 added requirements with
satisfyandverifylinks.
Now we bring all of these into one model.
Package Organization
A complete model needs clear package organization. Here is a practical layout:
package CoffeeMachineDomain {
import ScalarValues::*;
// Domain vocabulary
item def Water;
item def CoffeeBeans;
item def BrewedCoffee;
enum def GrindSetting {
fine;
medium;
coarse;
}
}
package CoffeeMachineStructure {
import ScalarValues::*;
import CoffeeMachineDomain::*;
port def BeanInputPort {
in item beans : CoffeeBeans;
}
port def GroundCoffeePort {
out item grounds : CoffeeBeans;
}
port def WaterPort {
in item water : Water;
}
port def WaterSupplyPort {
out item water : Water;
}
port def BrewOutputPort {
out item coffee : BrewedCoffee;
}
part def Grinder {
attribute grindSize : GrindSetting;
port beanInput : BeanInputPort;
port groundOutput : GroundCoffeePort;
}
part def Brewer {
attribute waterTemp : Real;
attribute brewPressure : Real;
port waterInlet : WaterPort;
port groundInput : BeanInputPort;
port brewOutput : BrewOutputPort;
}
part def WaterTank {
attribute currentLevel : Real;
attribute maxCapacity : Real;
port waterOut : WaterSupplyPort;
}
part def CoffeeMachine {
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
attribute serialNumber : String;
connect waterTank.waterOut to brewer.waterInlet;
connect grinder.groundOutput to brewer.groundInput;
}
}
package CoffeeMachineBehavior {
import CoffeeMachineStructure::*;
action def GrindBeans {
in item beans : CoffeeBeans;
}
action def HeatWater;
action def ExtractCoffee {
in item water : Water;
in item grounds : CoffeeBeans;
out item coffee : BrewedCoffee;
}
action def DispenseCoffee {
in item coffee : BrewedCoffee;
}
action def BrewCycle {
action grind : GrindBeans;
action heat : HeatWater;
action extract : ExtractCoffee;
action dispense : DispenseCoffee;
first grind then heat;
// After heating, extract and dispense in sequence
first heat then extract;
first extract then dispense;
}
state def Idle;
state def Brewing;
state def Cleaning;
state def Error;
state def MachineStates {
state idle : Idle;
state brewing : Brewing;
state cleaning : Cleaning;
state error : Error;
transition idleToBrewing
first idle
then brewing;
transition brewingToIdle
first brewing
then idle;
transition idleToCleaning
first idle
then cleaning;
transition cleaningToIdle
first cleaning
then idle;
transition anyToError
first idle
then error;
}
}
package CoffeeMachineAnalysis {
import ScalarValues::*;
calc def BrewTempTarget {
in ambient : Real;
return result : Real;
result = if ambient < 18.0 ? 94.0 else 93.0;
}
constraint def BrewTempSafe {
in temp : Real;
temp >= 90.0 and temp <= 96.0;
}
constraint def SafetyInterlock {
in cupDetected : Boolean;
in brewCommand : Boolean;
not brewCommand or cupDetected;
}
}
package CoffeeMachineRequirements {
import ScalarValues::*;
import CoffeeMachineStructure::*;
import CoffeeMachineAnalysis::*;
requirement def <'REQ-TEMP-001'> BrewTempReq {
doc /* The extraction temperature shall remain between 90 and 96 degrees Celsius
during the brew cycle. */
subject coffeeMachine : CoffeeMachine;
}
requirement def <'REQ-SAFE-001'> SafetyInterlockReq {
doc /* The system shall not begin a brew cycle unless a cup is detected
on the drip tray. */
subject coffeeMachine : CoffeeMachine;
}
requirement def <'REQ-TIME-001'> BrewTimeReq {
doc /* A standard brew cycle shall complete within 60 seconds. */
subject coffeeMachine : CoffeeMachine;
}
}
package CoffeeMachineVerification {
import CoffeeMachineRequirements::*;
verification def BrewTempTest {
doc /* Measure extraction temperature during a standard brew cycle. */
objective {
verify requirement : BrewTempReq;
}
}
verification def SafetyInterlockTest {
doc /* Attempt to start a brew cycle without a cup. Confirm the system
refuses. */
objective {
verify requirement : SafetyInterlockReq;
}
}
verification def BrewTimeTest {
doc /* Time a standard brew cycle from start signal to dispensing
complete. */
objective {
verify requirement : BrewTimeReq;
}
}
}
How the Packages Connect
The packages form a clear dependency chain:
CoffeeMachineDomain (items, enums -- no imports)
↓
CoffeeMachineStructure (parts, ports, connections -- imports Domain)
↓
CoffeeMachineBehavior (actions, states -- imports Structure)
↓
CoffeeMachineAnalysis (calcs, constraints -- imports ScalarValues)
↓
CoffeeMachineRequirements (requirements -- imports Structure + Analysis)
↓
CoffeeMachineVerification (verification cases -- imports Requirements)
Each package depends only on packages above it. No circular imports. A reviewer can read the model top-down: vocabulary, then structure, then behavior, then analysis, then requirements, then verification.
Cross-Concern Traceability
The real power of a complete model is traceability across concerns. From any requirement, you can follow links to:
- The design element that satisfies it (via
subjecton the requirement) - The constraint that formalizes its condition (in the Analysis package)
- The verification case that will produce evidence (via
verify) - The state or action that exercises the relevant behavior
For example, BrewTempReq traces to:
CoffeeMachine(the subject that must satisfy it)BrewTempSafeconstraint (the formal condition: 90–96 C)BrewTempTestverification case (how you prove compliance)BrewCycleaction (the behavior under test)
This is the traceability loop that SysML v2 is designed for. Without it, requirements live in spreadsheets, design lives in diagrams, and verification lives in test plans – all disconnected. With it, everything is linked in one reviewable model.
What Makes This Model Review-Ready
A reviewer looking at this model can answer key questions quickly:
- What is the system made of? Read
CoffeeMachineStructure. Every component, port, and connection is visible. - What does it do? Read
CoffeeMachineBehavior. The brew cycle sequence and operating modes are explicit. - What must it satisfy? Read
CoffeeMachineRequirements. Every requirement has a subject and formal text. - How will we verify it? Read
CoffeeMachineVerification. Every requirement has a planned verification case. - What constraints apply? Read
CoffeeMachineAnalysis. Safety interlocks and temperature bounds are checkable.
No concern is hidden. No link is implicit. That is the goal.
The Model on Disk
Everything in this book is snippets of one model, and that model exists as files.
If you cloned this repository, it is under examples/coffee-machine/ – fifteen
.sysml files plus a sysml.toml, checkable with sysml check.
Reading a whole model is a different experience from reading it a construct at a time, and it is worth doing once. Here is which file goes with which chapter:
| File | Chapter |
|---|---|
definitions.sysml | 3 – definitions and usages |
package-structure.sysml | 4 – packages, visibility, public import |
ports-and-interfaces.sysml | 5, 6 – ports and interface definitions |
connections.sysml | 6 – connections and connectors |
flows.sysml | 6 – flows and messages |
actions.sysml | 7 – actions and control behaviour |
brew-cycle-flow.sysml | 7 – the brew cycle alone, for diagram generation |
states.sysml | 8 – states and transitions |
calculations.sysml | 9 – calculations and constraints |
requirements.sysml | 10 – requirements and verification |
typing-and-specialization.sysml | 12 – typing, specialization, redefinition |
metadata.sysml | 13 – metadata and documentation |
views.sysml | 13 – viewpoints, views, renderings |
orchestration.sysml | 16 – multi-subsystem simulation |
demo-analysis.sysml | 10, 16 – an analysis case |
Two other example projects sit beside it: examples/beverage-workspace/, the
multi-project workspace Chapter 15 walks
through, and examples/views-library/, eleven small models that each isolate one
piece of the view chain.
A caveat so you are not surprised: views.sysml reports three diagnostics about
satisfying a viewpoint. The model is correct – a viewpoint definition is a kind
of requirement definition, so satisfying one is what the specification intends –
and the resolver in the current tool does not accept it. The file is left as the
specification says it should be written.
Exploring the Model
The complete coffee machine model can be explored from four different perspectives. Each tab shows the same underlying model through a different diagram view:
Extending the Model
This model is a foundation, not a finished product. Real projects extend it in several directions:
- More requirements. Add power consumption, noise level, maintenance interval requirements.
- More states. Add
Steaming,Descaling,Standbymodes with guarded transitions. - More actions. Add
SteamMilk,RunDescaleactions with their own control flow. - Variants. Use specialization (Chapter 12) to create
EspressoMachineandDripMachinevariants. - Governance. Use metadata and views (Chapter 13) to add review status and stakeholder-specific model slices.
- Physical units. Use library quantities (Chapter 14) to replace
RealwithTemperatureValueandPressureValue.
The structure is ready for all of these. Each extension adds to the model without rewriting what exists.
Specialization and Redefinition
By now you have a complete coffee machine model: definitions, usages, ports, connections, actions, states, constraints, and requirements. Everything so far has been about building one machine. But what happens when you need a second kind of machine – one that shares most of the original design but changes a few things?
You could copy CoffeeMachine, rename it, and edit the copy. That works once. It becomes a maintenance problem the moment you fix a bug in the original and forget to propagate it to the copy. SysML v2 gives you better tools: specialization and redefinition. This chapter covers four related mechanisms for refining types without duplicating them.
Specialization
Specialization says: this new type is a kind of an existing type. It inherits everything from the general type and can add more.
The operator is :>.
package CoffeeMachineVariants {
import CoffeeMachineDomain::*;
part def EspressoMachine :> CoffeeMachine {
attribute pumpPressure : Real;
}
}
EspressoMachine :> CoffeeMachine means every EspressoMachine is a CoffeeMachine. It inherits all of CoffeeMachine’s features – the grinder, the brewer, the waterTank, the serialNumber. Then it adds pumpPressure, which only espresso machines have.
You have actually been using specialization implicitly since Chapter 2. When you write part def Grinder, it implicitly subclassifies the base library definition Parts::Part. When you write attribute waterTemp : Real, the attribute usage implicitly subsets the base library usage Attributes::attributeValues. Definitions get an implicit subclassification, usages an implicit subsetting, and writing your own explicit specialization suppresses the implicit one (SysML §7.6.8). The :> operator just makes the relationship explicit between your own types.
When to specialize
Specialize when the new type genuinely is a kind of the base type. Every instance of the specialized type should be substitutable wherever the general type is expected.
Good uses:
EspressoMachine :> CoffeeMachine– an espresso machine is a coffee machine.PressurizedBrewer :> Brewer– a pressurized brewer is a brewer.LargeWaterTank :> WaterTank– it is still a water tank, just bigger.
Bad uses:
Grinder :> CoffeeMachine– a grinder is not a coffee machine.BrewCycle :> CoffeeMachine– an action is not a structural type.
If the “is a kind of” sentence does not sound right in plain English, specialization is probably the wrong tool.
Multiple specialization
A definition can specialize more than one general type:
part def HeatedGrinder :> Grinder, HeatedComponent {
attribute grindTemp : Real;
}
HeatedGrinder inherits from both Grinder and HeatedComponent. This is useful when a concept belongs to two categories simultaneously. Use it sparingly – deep multiple-inheritance hierarchies become difficult to reason about.
Redefinition
Specialization adds new features. Redefinition replaces an inherited feature with a more specific version. The operator is :>>.
Suppose CoffeeMachine has a general target temperature attribute:
part def CoffeeMachine {
attribute targetTemp : Real;
// ... other features
}
An espresso machine needs that temperature locked to a specific value:
part def EspressoMachine :> CoffeeMachine {
attribute brewTemp :>> targetTemp = 94.0;
}
The line attribute brewTemp :>> targetTemp = 94.0 does three things at once:
- It says
brewTempredefines the inheritedtargetTemp. - It gives the feature a new name in this context:
brewTempinstead oftargetTemp. - It binds the value
94.0.
That third one is a binding, not a suggestion: a bare = asserts the value for every instance of the type. If you want a value a particular usage can still override, write default = 94.0 instead (SysML §7.6.3, and Chapter 5).
Inside an EspressoMachine, there is no separate targetTemp alongside brewTemp. The inherited feature is brewTemp. If you access targetTemp on an EspressoMachine instance, you get the same feature that brewTemp names.
Redefinition with tighter typing
You can also redefine a feature to narrow its type:
part def CoffeeMachine {
part brewer : Brewer;
}
part def EspressoMachine :> CoffeeMachine {
part espressoBrewer :>> brewer : PressurizedBrewer;
}
The inherited brewer is redefined to be typed by PressurizedBrewer (which itself specializes Brewer). The redefined type must be compatible with the original – you cannot redefine a Brewer as a WaterTank. Redefinition is a kind of subsetting, and a subsetting feature’s type has to specialize the type of what it subsets (KerML §8.3.3.3.10).
When to redefine
Redefine when an inherited feature needs to be more specific in the specialized context. Common cases:
- Binding a value:
:>> targetTemp = 94.0 - Narrowing a type:
:>> brewer : PressurizedBrewer - Tightening multiplicity:
:>> sensors [2]on a base that allows[1..*]
Do not redefine for cosmetic renaming alone. If the inherited name is fine and the type does not change, there is nothing to redefine.
Subsetting
Subsetting narrows a collection. Where redefinition replaces a feature, subsetting says: this feature is a subset of that inherited collection.
The operator is :> applied to a usage (not a definition). Context determines which relationship you get: on a definition, :> is subclassification; on a usage, it is subsetting (SysML §7.6.2, §7.6.3). The long spellings make it explicit – specializes on definitions, subsets on usages.
part def CoffeeMachine {
part sensors : Sensor[1..*];
}
part def EspressoMachine :> CoffeeMachine {
part tempSensors :> sensors : TemperatureSensor[1..2];
part pressureSensors :> sensors : PressureSensor[1];
}
tempSensors :> sensors says the temperature sensors are a subset of the total sensors collection. pressureSensors :> sensors is another subset. Both are parts of the same inherited sensors group, but they carve it into named subsets with more specific types.
The key difference from redefinition: subsetting does not replace the inherited feature. The sensors collection still exists. tempSensors and pressureSensors are portions of it.
When to subset vs. redefine
- Redefine when the specialized type has exactly one version of the inherited feature, and you want to replace it entirely.
- Subset when the inherited feature is a collection and you want to name specific portions of it without replacing the whole.
If the base type has part brewer : Brewer (a single part), redefine it. If the base type has part sensors : Sensor[1..*] (a collection), subset it.
Conjugation
Conjugation makes a type inherit another type’s features with their input and output directions reversed (SysML §7.12.3). The operator is ~ (tilde) placed before a type name.
You saw ports in Chapter 5. A port definition declares features with directions:
port def WaterSupplyPort {
out item water : Water;
}
When a water tank sends water out, it uses WaterSupplyPort directly. But the brewer that receives that water needs the same port shape with the direction flipped – out becomes in. Chapter 5 handled that by defining a second port type by hand: WaterPort, with in item water. Conjugation gets you the receiving side for free instead:
part def WaterTank {
port waterOut : WaterSupplyPort;
}
part def Brewer {
port waterInlet : ~WaterSupplyPort;
}
~WaterSupplyPort is WaterSupplyPort with all directions reversed. The out item water becomes in item water. The port shapes are structurally compatible – they carry the same items in opposite directions – which is exactly what you need for a connection.
When to conjugate
Conjugate when two components interact through complementary ports and the only difference is direction. This is the standard pattern for producer/consumer, sender/receiver, and request/response pairs.
Do not conjugate when the two sides of an interaction carry different items or have different structure. In that case, define two separate port types.
Putting It Together: The EspressoMachine
Here is a fuller example that uses specialization, redefinition, and conjugation together:
package CoffeeMachineVariants {
import ScalarValues::*;
import CoffeeMachineDomain::*;
part def PressurizedBrewer :> Brewer {
attribute boilerPressure : Real;
}
part def EspressoMachine :> CoffeeMachine {
// Redefine the brewer to a pressurized variant, and pin the
// water temperature it inherits from Brewer
part espressoBrewer :>> brewer : PressurizedBrewer {
attribute :>> waterTemp = 94.0;
}
// Add espresso-specific features
attribute shotVolume : Real;
}
}
Read this from top to bottom:
PressurizedBrewer :> Brewer– a new brewer type that addsboilerPressure.EspressoMachine :> CoffeeMachine– inherits grinder, brewer, water tank, serial number.espressoBrewer :>> brewer– the inheritedbrewerpart is redefined to usePressurizedBrewer.:>> waterTemp = 94.0– the brewer’s water temperature is bound to 94.0. Note where this line sits: insideespressoBrewer, becausewaterTempis a feature ofBrewer, not ofCoffeeMachine. A redefinition can only target a feature the enclosing type actually inherits, soattribute brewTemp :>> waterTempwritten directly inEspressoMachinewould fail to resolve.shotVolume– a new attribute that only espresso machines have.
That fourth line also shows a shortcut worth knowing: a redefining usage with no declared name takes the name of the feature it redefines (SysML §7.6.5), so this one is still called waterTemp.
An EspressoMachine instance has everything a CoffeeMachine has, plus the refinements. Any constraint, requirement, or verification case that references CoffeeMachine applies to EspressoMachine too, because specialization preserves that relationship.
Decision Guide
When you need to refine a type, choose the simplest mechanism that fits:
| Situation | Mechanism | Operator | Example |
|---|---|---|---|
| New type is a kind of existing type | Specialization | :> | EspressoMachine :> CoffeeMachine |
| Inherited feature needs a new default or narrower type | Redefinition | :>> | brewTemp :>> targetTemp = 94.0 |
| Inherited collection needs named subgroups | Subsetting | :> on usage | tempSensors :> sensors |
| Port directions need to be flipped | Conjugation | ~ | port waterInlet : ~WaterSupplyPort |
| Just adding new features to a type | Specialization alone | :> | Add pumpPressure in subtype |
| Replacing a part with a more specific variant | Redefinition | :>> | espressoBrewer :>> brewer |
| Documenting a family of named alternatives | Variation | variation / variant | see below |
Start at the top of this table. If specialization alone solves your problem, stop there. Reach for redefinition only when you need to change something inherited, not just add to it. Use subsetting only for genuine collections. Use conjugation only for direction inversion.
Variation
Sometimes a design has several named alternatives that share a common base by deliberate choice, rather than one refining the next. A coffee machine might offer two brew profiles – standard and intense – where neither is a refinement of the other; both are options the customer picks between.
variation <keyword> def <ChoiceName> :> <BaseName> {
variant <keyword> <name> : <AlternativeName>;
variant <keyword> <name> : <AlternativeName>;
}
part def BrewProfile {
attribute targetTemp : Real;
}
part def StandardBrew :> BrewProfile;
part def IntenseBrew :> BrewProfile;
variation part def BrewProfileChoice :> BrewProfile {
variant part standard : StandardBrew;
variant part intense : IntenseBrew;
}
part def CoffeeMachine {
part brewProfile : BrewProfileChoice;
}
BrewProfileChoice is a variation – a variation point, marked by the variation keyword before the kind keyword. Its members are its variants, each marked variant. A brewProfile typed by BrewProfileChoice resolves to exactly one of them per configuration.
The direction of those two keywords is what people get backwards. variation goes on the choice, not on the alternatives, and variant goes on the members inside it. Five rules follow, and the specification states all of them (SysML §7.6.7, with the formal constraints at §8.3.6.2):
- Variant usages may only be declared inside a variation.
- Every usage in a variation’s body is a variant, so a variation cannot carry ordinary features of its own. That is why
targetTemplives onBrewProfileand reachesBrewProfileChoiceby inheritance. - A variation is always abstract. You never write
abstracton it, and you never instantiate the choice – only a variant. - The kind of a variant must match the kind of its variation:
variant partinside avariation part def. - A variation may not specialize another variation. You can build a variation on an ordinary definition, as
BrewProfileChoicebuilds onBrewProfile, but you cannot chain choices together – a choice of choices is not a thing (validateDefinitionVariationSpecialization).
Usages can be variation points too, with the same shape – variation part transmission : Transmission { variant ...; } turns a single usage into a choice rather than a whole definition.
The point of variation is not refinement – it is alternation. Where EspressoMachine :> CoffeeMachine says every EspressoMachine is a CoffeeMachine, a variation says a given configuration picks one of several peers.
Use variation when the model genuinely enumerates alternatives the system can be configured into. When the relationship is “this is a more specific kind of that,” plain specialization is clearer; variation is the right tool only for the “choose one of these” pattern.
Elaboration: The Implicit Pass
Everything in this chapter – specialization, redefinition, subsetting, conjugation, variation – is something you write. But the model is also understood to gain relationships you did not write down. Weaving those in is called elaboration: the model is read as if a small set of implicit relationships were filled in for you.
Suppose a library declares:
part def Grinder {
attribute rpm : Real;
}
and your model specializes it:
part def HeatedGrinder :> Grinder {
attribute heatLevel : Real;
}
You wrote one :>, but HeatedGrinder is now understood to have both rpm (inherited from Grinder) and heatLevel. You did not redeclare rpm; elaboration supplies it. The same is true when specialization chains: declare BurrGrinder :> HeatedGrinder on top of HeatedGrinder :> Grinder, and the generalization from BurrGrinder all the way up to Grinder is implicit, so a constraint that references Grinder also sees every specialization of it as a valid operand.
The same idea fills in two more structures:
- Library-defined features. Standard-library supertypes (such as
Part) carry features the spec says every specialization gains; you don’t write them, but elaboration treats them as present. - Subsetting portions. When
tempSensors :> sensorsandpressureSensors :> sensorsboth subset the same collection, elaboration recognizes them as portions of the one inheritedsensorscollection, so they participate together in any query oversensors.
The point of elaboration is that you model in the direct relationships and the spec supplies the transitive ones. You don’t have to flatten the chain, redeclare inherited features, or enumerate library extensions – they are understood to exist because the spec says they do.
sysml-rs extends the spec here. The relationships elaboration adds – inherited features, transitive generalizations, library-provided extensions, subsetting-portion structure – are all spec. What sysml-rs chooses is when to realize them: it materializes them at elaboration time (eagerly, before the model is queried) rather than deriving them lazily on each request. The visible effect is the same; only the timing is the tool’s choice.
Common Mistakes
Specializing when composition is enough. If you just need a coffee machine with different parts inside, you do not need a new definition. Create a usage and configure it:
// Unnecessary specialization:
part def LargeCoffeeMachine :> CoffeeMachine {
part bigTank :>> waterTank : LargeWaterTank;
}
// Often sufficient: just a configured usage
part largeMachine : CoffeeMachine {
part bigTank :>> waterTank : LargeWaterTank;
}
The second form creates a specific usage of CoffeeMachine with a redefined water tank. No new definition needed. Specialize only when other parts of your model need to reference the new type by name – in constraints, requirements, or further specializations.
Redefining without a semantic reason. Renaming an inherited feature just because you prefer a different name adds noise to the model. Redefinition is a semantic statement: “this feature means something more specific here.” If the inherited name and type are fine, leave them alone.
Defining mirrored port types by hand. If you see two port definitions that are identical except that in and out are swapped, replace one with a conjugation. Two definitions means two places to update when the port structure changes. Conjugation keeps them automatically synchronized.
What You Have Built
The coffee machine now has a family, not just a shape. EspressoMachine
specializes CoffeeMachine, redefines the brewer to a pressurized one and pins
the temperature it inherits, and adds what only espresso machines have.
Four relationships did that work, and they are worth keeping straight:
- Specialization (
:>on a definition) – a more specific kind. - Redefinition (
:>>) – an inherited feature, made more specific here. - Subsetting (
:>on a usage) – this feature’s values are among those. - Conjugation (
~) – the same interface with its directions reversed.
Variation is the odd one out, and deliberately so: it is alternation rather
than refinement. EspressoMachine :> CoffeeMachine says every espresso machine
is a coffee machine. A variation says a configuration picks one of several peers.
In Chapter 13 the model starts carrying information about itself – maturity, ownership, safety classification – and you will use that to generate views for particular readers.
Metadata and Views
Your coffee machine model is built. It has structure, behavior, states, constraints, requirements, and verification cases. But models do not exist in isolation. People need to review them, approve them, and understand them – and different people care about different things.
This chapter covers two features that serve that human side of modeling: metadata for attaching governance information to model elements, and views for presenting the right slice of the model to the right stakeholder.
Comments vs. doc vs. Metadata
You have already used // comments throughout the model. Comments are notes in the source text. A tool ignores them during analysis. They are for the person reading the .sysml file, not for the model itself.
SysML v2 provides two more layers for human-oriented information, and the distinction matters.
doc is a model-level documentation string. Unlike a comment, doc is part of the model. A tool can extract it, display it in a hover tooltip, or include it in a generated report. You place it inside the element it describes:
part def Brewer {
doc /* Heats water and forces it through ground coffee
under controlled pressure and temperature. */
attribute waterTemp : Real;
attribute brewPressure : Real;
}
Metadata is structured, queryable data attached to model elements. Review status, maturity level, safety classification, responsible team – these are not free text. They have known fields and known values, and you need to filter or report on them. Metadata is the right tool for that job.
The rule of thumb: if it must be queried, filtered, or enforced by tooling, make it metadata. If it explains intent to a human reader, use doc. If it is a local note to yourself, use a comment.
Defining Metadata
A metadata definition looks like any other definition in SysML v2. You declare it with metadata def, and it can have attributes:
package CoffeeMachineGovernance {
import ScalarValues::*;
metadata def ReviewStatus {
attribute status : String;
attribute reviewer : String;
}
metadata def SafetyClass {
attribute level : String;
}
}
ReviewStatus is now a reusable type. It says: anything that carries a review status has a status string and a reviewer string. You define it once, in one package, and every team uses the same structure.
This is important. If one team invents ReviewInfo with a state field and another invents ApprovalStatus with a currentStatus field, you end up with incompatible schemas that no tool can query uniformly. Centralize your metadata definitions early.
Annotating Elements with Metadata
To attach metadata to a model element, you use the @ annotation syntax. The annotation goes immediately before the element it describes:
package CoffeeMachineStructure {
import ScalarValues::*;
import CoffeeMachineGovernance::*;
@ReviewStatus {
:>> status = "approved";
:>> reviewer = "Systems Team";
}
part def CoffeeMachine {
doc /* Top-level system definition for the Smart Coffee Machine. */
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
attribute serialNumber : String;
}
}
The @ReviewStatus { ... } block annotates the CoffeeMachine definition. The :>> syntax redefines the metadata attributes with specific values. After this annotation, any tool that queries the model can determine that CoffeeMachine has been approved by the Systems Team.
You can annotate any element – definitions, usages, requirements, actions, states. The annotation always appears directly before the element:
@SafetyClass { :>> level = "SIL-2"; }
requirement def SafetyInterlockReq {
doc /* The machine shall disable the heater if water
temperature exceeds 100 degrees Celsius. */
}
Now the safety interlock requirement carries its safety integrity level as structured data, not as a note buried in a comment.
Multiple Annotations
An element can carry more than one annotation. Stack them:
@ReviewStatus {
:>> status = "in-review";
:>> reviewer = "Safety Board";
}
@SafetyClass { :>> level = "SIL-2"; }
part def Brewer {
doc /* Heating subsystem. Under safety review. */
attribute waterTemp : Real;
attribute brewPressure : Real;
}
The Brewer now carries both governance metadata and safety classification. A review dashboard could show all elements where status is "in-review", or all elements classified as SIL-2.
A Maturity Tracking Example
Metadata is especially useful for tracking model maturity across a project lifecycle. Here is a pattern that covers the typical progression:
package CoffeeMachineGovernance {
import ScalarValues::*;
metadata def ReviewStatus {
attribute status : String;
attribute reviewer : String;
}
metadata def SafetyClass {
attribute level : String;
}
metadata def MaturityLevel {
attribute level : String;
}
}
Then, as the model evolves, annotations track where each element stands:
@MaturityLevel { :>> level = "preliminary"; }
action def BrewCycle { /* ... */ }
@MaturityLevel { :>> level = "baselined"; }
part def WaterTank { /* ... */ }
@MaturityLevel { :>> level = "verified"; }
requirement def BrewTempReq { /* ... */ }
A project manager does not need to understand the brew cycle control flow to know that the action definition is still preliminary. The metadata tells them directly.
From Metadata to Views
Metadata tells tools about the status of individual elements. But stakeholders do not review individual elements. They review slices of the model that are relevant to their concerns. A safety engineer cares about hazards, interlocks, and safety-classified requirements. An architect cares about structural decomposition and interfaces. Showing either of them the entire model is counterproductive.
SysML v2 formalizes this with two concepts: viewpoints and views.
Viewpoints: Defining Stakeholder Concerns
A viewpoint declares what a particular stakeholder cares about. It does not select specific model elements – it describes the kind of information that matters for a particular review purpose.
package CoffeeMachineViews {
viewpoint def SafetyViewpoint {
doc /* Concerns: hazard mitigations, safety interlocks,
and safety-classified requirements. */
}
viewpoint def ArchitectureViewpoint {
doc /* Concerns: structural decomposition, subsystem
interfaces, and key part definitions. */
}
}
Think of a viewpoint as a lens. SafetyViewpoint says: when you look through this lens, you want to see safety-related elements. ArchitectureViewpoint says: through this lens, you want to see structure.
A viewpoint can also specify stakeholder to name who uses it, and purpose to explain why:
viewpoint def VerificationViewpoint {
doc /* Requirement-to-test traceability for verification closure. */
}
Views: Selecting Model Content
A view usage is the concrete selection. It is typed by a view def, can satisfy one or more viewpoints, and uses expose to pull specific elements into the view.
Before getting into stakeholder-specific views, it helps to see the simplest possible case: one view definition, one usage, one expose. This is the running anchor we will refine through the rest of the chapter.
package CoffeeMachineViews {
import CoffeeMachineStructure::*;
view def CoffeeOverview;
view overview : CoffeeOverview {
expose CoffeeMachine;
}
}
That’s a complete, valid view: a definition that names the kind of view, and a usage that picks up the definition and exposes the top-level part. Anything that walks the model can ask “what does the overview view contain?” and get back the CoffeeMachine element and – by transitive containment – everything inside it.
Notice which half carries the expose. The grammar is strict about this division of labour: a view definition body admits filter and render clauses (plus ordinary definition members), while expose is legal only in a view usage body. The definition is the reusable recipe – what kinds of elements, presented how. The usage is the concrete selection – these elements, from this model. Keep that split in mind through the rest of the chapter; it is the single easiest thing to get backwards. (sysml-rs’s parser currently accepts an expose inside a view def without a diagnostic – a known parser gap, not a licence. The construct is not in the grammar, and conformant tools reject it.)
Now, the more interesting case. A view usage can satisfy one or more viewpoints, and can expose more than one element. satisfy reaches a viewpoint because a ViewpointDefinition is a RequirementDefinition in the abstract syntax – satisfying a viewpoint and satisfying a requirement are the same relationship:
package CoffeeMachineViews {
import CoffeeMachineStructure::*;
import CoffeeMachineRequirements::*;
viewpoint def SafetyViewpoint {
doc /* Concerns: hazard mitigations and safety interlocks. */
}
viewpoint def ArchitectureViewpoint {
doc /* Concerns: structural decomposition and interfaces. */
}
view def SafetyReviewView;
view def ArchitectureReviewView;
view safetyReview : SafetyReviewView {
satisfy SafetyViewpoint;
expose CoffeeMachineRequirements::SafetyInterlockReq;
expose CoffeeMachineRequirements::BrewTempReq;
}
view architectureReview : ArchitectureReviewView {
satisfy ArchitectureViewpoint;
expose CoffeeMachineStructure::CoffeeMachine;
expose CoffeeMachineStructure::Brewer;
expose CoffeeMachineStructure::Grinder;
expose CoffeeMachineStructure::WaterTank;
}
}
expose is the key operation. It does not copy the element into the view. It references it. The safetyReview view usage satisfies SafetyViewpoint and exposes two requirements. The architectureReview view usage satisfies ArchitectureViewpoint and exposes four structural definitions. A tool rendering the safety review shows only the safety requirements – the grinder and its grind settings are nowhere in sight.
This is the critical difference between a view and a package. A package owns its members. A view exposes references to members that live elsewhere. The underlying model is unchanged. Views are projections, not copies.
Two flavours of expose: member and namespace
expose comes in two forms, and the choice is more than syntactic sugar.
view interfaceReview : ArchitectureReviewView {
expose CoffeeMachineStructure::Brewer; // member expose
expose CoffeeMachineStructure::*; // namespace expose
}
The first form is a member expose: it pulls one named element into the view. The second is a namespace expose: it pulls every member of the named namespace into the view. The trailing ::* is the cue.
Member expose is precise; namespace expose is sweeping. You reach for namespace expose when the natural unit of review is a whole package – the safety package, the requirements package, the API surface – and you do not want the view to drift behind the package as new elements are added. Once CoffeeMachineStructure::* is exposed, anything new you drop into that package is automatically visible in the view.
Both forms are subject to the same qualified-name resolution. If you misspell the qname, the view exposes nothing for that line. There is no error – the line is parsed, the qname does not resolve, and tools simply have nothing to show. That silent failure mode is one of the more common authoring mistakes; we return to it in Common Mistakes below.
Anonymous view usages
A view usage does not have to be named. When you want a one-off slice of the model – to share a screenshot in a review, to drop a quick visualization into a diagram, to test what a particular expose would surface – you can write the usage inline without giving it a name:
view scratch { expose CoffeeMachine; }
That is a complete view usage. There is no view def, no satisfaction, no rendering – just a name and a body. Tools treat it like any other view usage: they resolve the expose, project the element, and render the result. Anonymous views are the modeling equivalent of a scratch buffer. Reach for them when you want a slice now and have not yet decided whether the slice deserves a definition.
Combining Metadata and Views
Metadata and views work well together. You can use metadata annotations to mark which elements belong in which review, and then build views that surface exactly those elements:
package CoffeeMachineReview {
import ScalarValues::*;
import CoffeeMachineGovernance::*;
import CoffeeMachineStructure::*;
import CoffeeMachineRequirements::*;
viewpoint def SafetyViewpoint {
doc /* All elements classified SIL-2 or above,
plus their associated requirements. */
}
view def SafetyReviewView;
// The safety review exposes only safety-relevant elements
view safetyReview : SafetyReviewView {
satisfy SafetyViewpoint;
expose CoffeeMachineRequirements::SafetyInterlockReq;
expose CoffeeMachineRequirements::BrewTempReq;
expose CoffeeMachineStructure::Brewer;
}
viewpoint def ArchitectureViewpoint {
doc /* System decomposition for architecture board review. */
}
view def ArchitectureReviewView;
// The architecture review exposes structural elements
view architectureReview : ArchitectureReviewView {
satisfy ArchitectureViewpoint;
expose CoffeeMachineStructure::CoffeeMachine;
expose CoffeeMachineStructure::Grinder;
expose CoffeeMachineStructure::Brewer;
expose CoffeeMachineStructure::WaterTank;
}
}
Notice that Brewer appears in both views. The safety engineer sees it because it contains the heater that is safety-classified. The architect sees it because it is a major subsystem. Same element, different reasons, different context. The model does not duplicate anything.
Filtering a View with filter
expose says “pull this element (or namespace) in”. Sometimes you want the opposite shape: pull a whole namespace in, then filter out the parts that do not belong in this particular view. That is what filter is for.
Note where each half lives: expose sits in the view usage, while filter typically sits in the view definition, where it becomes part of the reusable recipe and screens whatever any usage of that definition exposes. (A usage body may carry its own filter too, for one-off narrowing.)
A filter clause is a Boolean expression evaluated once per element that the view exposes. If the expression is true, the element stays in the view; if it is false, the element is dropped.
The spec’s own filter idiom is a classification test: the @ operator asks whether the current element is classified by a metaclass or a metadata definition. That is the portable form, and it is what the standard library’s own filtered packages use:
view def PartsOnlyPortable {
filter @SysML::PartUsage; // metaclass test
}
view partsOnly : PartsOnlyPortable {
expose CoffeeMachineStructure::*;
}
view def MandatorySafety {
filter @SafetyClassification and SafetyClassification::level == "SIL-2";
}
view mandatorySafety : MandatorySafety {
expose CoffeeMachineStructure::*;
}
The second form is the pattern for filtering on a metadata field: classify with @, then reach into the annotation’s attribute through a ::-qualified reference.
The simplest filters are literal:
view def AllOfIt { filter true; } // accept everything
view def NoneOfIt { filter false; } // accept nothing
filter true and filter false are useful as scaffolding – a starting point you can sharpen.
sysml-rs extends the spec here. In addition to the spec’s classification tests, sysml-rs’s filter evaluator binds four convenience names for the element under test:
self(the element),kind(its element kind, as a string),name(its declared name), andid(its identifier). These are not spec language – another tool will not resolve them. They are convenient for exploratory slices in this toolchain; use@classification tests for filters that have to travel.
With those bindings in hand, the next step up is a predicate over kind or name:
view def PartsOnly {
filter kind == "PartUsage";
}
view def JustTheBrewer {
filter name == "Brewer";
}
view structureParts : PartsOnly {
expose CoffeeMachineStructure::*;
}
view brewerOnly : JustTheBrewer {
expose CoffeeMachineStructure::*;
}
structureParts exposes everything in CoffeeMachineStructure; its definition keeps only the elements whose kind is "PartUsage". brewerOnly exposes the same package; its definition keeps only the element named "Brewer". The kind == ... predicate is the most common shape because it lets you say “the structural slice”, “the requirements slice”, “the actions slice” without listing each element by hand.
You can stack filters. Multiple filter clauses on the same view compose as logical AND: an element passes only if every filter accepts it.
view def SafetyParts {
filter kind == "PartUsage"; // first filter
filter name != "Grinder"; // second filter
}
view safetyParts : SafetyParts {
expose CoffeeMachineStructure::*;
}
safetyParts is the parts of the structure package that are not the grinder. Both filters must return true for an element to survive. Stacking is how you build up a complex viewCondition from small, readable predicates.
sysml-rs extends the spec here. The spec defines
ElementFilterMembership– a Boolean expression bound to a view – but does not mandate what happens when that expression cannot be evaluated. sysml-rs’s evaluator is conservative: afilterexpression that the runtime cannot reach (an unsupported operator, a reference that does not resolve, a type the evaluator does not yet handle) falls through totrue. The element passes. The reasoning: a half-implemented filter that drops elements silently is more dangerous than one that admits too much. The default is over-inclusion, which the reader can see, rather than under-inclusion, which the reader cannot. Spec compliance: do not rely on this fallthrough as a contract – write filter expressions you are confident the evaluator supports, and treat any “this view shows more than I expected” surprise as a possible safe-default trip rather than a real model issue.
The expressions inside filter are real Boolean expressions, written in the same language as constraints (chapter 9). You can compose them with and, or, not; you can index members of self; you can compare id to a qualified-name literal. In practice, most filters stay close to the kind == "..." and name == "..." shape – those two predicates cover most stakeholder slices.
Renderings
A view says what to show. A rendering says how to show it. They are separate concepts because the same slice of the model can sensibly appear as a table, a tree, a diagram, or a matrix depending on who is reading it.
The spec gives you a small kit:
rendering def Foo;declares a rendering by name.- A view definition picks up a rendering with a
renderclause in its body. Two forms: name an existing rendering usage (render asTreeDiagram;– the standard library’sViewspackage shipsasTextualNotation,asTreeDiagram, andasInterconnectionDiagramready-made), or declare one inline from a rendering definition (render rendering standard : StandardRendering;). The bare-reference form is spec, but sysml-rs currently parses only the inline form; the runnable examples in this book use the inline form throughout. - A view usage can override its definition’s rendering by writing its own
renderclause in the usage body.
Here is the running coffee-machine example with a rendering attached:
package CoffeeMachineViews {
import CoffeeMachineStructure::*;
import CoffeeMachineRequirements::*;
rendering def StandardRendering;
view def SafetyReviewView {
render rendering standard : StandardRendering;
}
view safetyReview : SafetyReviewView {
satisfy SafetyViewpoint;
expose CoffeeMachineRequirements::SafetyInterlockReq;
expose CoffeeMachineRequirements::BrewTempReq;
}
}
The render rendering standard : StandardRendering; line is a ViewRenderingMembership: it declares a rendering usage inside the view and types it by the rendering definition. The view uses the rendering by owning a usage of it.
A rendering definition can carry attributes – things like layout direction, paper size, page break preferences – but most authors start with an empty rendering def and let the tool’s defaults take over. The point of the rendering is the binding: the view declares its intent, and the tool that consumes the view picks up the matching rendering and presents the slice accordingly.
What Kind of Diagram Does a View Become?
You have written view def SafetyReviewView and view def CoffeeOverview. When a tool renders one of these, what shape does it choose? A flat list? A tree? A diagram with boxes and lines? The answer is determined by the view definition’s supertype.
The standard library package StandardViewDefinitions defines exactly eight standard view definitions, each corresponding to a recognizable diagram shape. When you write view def Foo :> InterconnectionView, you are saying “this view should be presented as an interconnection diagram”. When you write view def Foo :> ActionFlowView, you are saying “this view should be presented as an action-flow diagram”. The supertype is the contract.
The eight standard view definitions are:
| Supertype | What it shows |
|---|---|
GeneralView | Any exposed elements, as a general graph of nodes and edges |
InterconnectionView | Features as (nested) nodes and the connections between them as edges |
ActionFlowView | Actions, their parameters, and the flows between them |
StateTransitionView | States and the transitions between them |
SequenceView | Time-ordered event occurrences and messages on lifelines |
GeometryView | Spatial items visualized in two or three dimensions |
GridView | Elements and relationships in a rectangular grid (tables, matrices) |
BrowserView | The hierarchical membership structure (containment tree) |
Pick the supertype that matches the question the view is answering. “Show me how the parts connect” → InterconnectionView. “Show me the order of actions in the brew cycle” → ActionFlowView. “Show me the state machine” → StateTransitionView. The supertype is the difference between a view that lands as a useful diagram and one that lands as a generic table.
Two diagram kinds you might expect are deliberately not on the list. There is no UseCaseView and no RequirementView in the standard library. For those, the library’s own documentation on GeneralView gives the recipe: specialize GeneralView and narrow it with a filter. A requirement view, for example, is a GeneralView filtered to requirement elements:
view def RequirementOverview :> GeneralView {
filter @SysML::RequirementDefinition or @SysML::RequirementUsage
or @SysML::SatisfyRequirementUsage;
}
The same pattern gives you a package view, a definition-and-usage view, or any other slice-by-kind: GeneralView plus the metaclass filter naming the kinds you want.
view def CoffeeStructure :> InterconnectionView;
view def BrewSequence :> ActionFlowView;
view def HeaterStateMachine :> StateTransitionView {
filter kind == "StateUsage" or kind == "TransitionUsage";
}
view structure : CoffeeStructure { expose CoffeeMachine; }
view brewOrder : BrewSequence { expose BrewCycle; }
view heaterStates : HeaterStateMachine { expose Brewer; }
Each of those views asks for model elements wrapped in a different presentation contract. The tool consuming them picks the appropriate diagram engine based on the supertype. You must write the :> explicitly. A view def Foo with no supertype is treated as the most general kind of view – GeneralView is the root of the family – and renders as a generic projection; the renderer does not infer a kind from the view’s name.
The Stakeholder → Viewpoint → View → Rendering Chain
Everything in this chapter so far has been one element at a time. Pulling them together: SysML v2’s view model is a single chain that runs from a person’s concern at one end to a rendered artifact at the other.
Stakeholder a person or role with a question
│
│ has
▼
Viewpoint the question, captured as a requirement
│ (`viewpoint def` is a kind of `requirement def`)
│ satisfied by
▼
View Usage the concrete view — exposes the content and declares
│ which viewpoints it satisfies
│ (typed by a View Definition: the reusable recipe —
│ supertype + filters + rendering;
│ `view def` is a kind of `part def`)
│ rendered through
▼
Rendering Usage the visual style applied
│
│ produces
▼
View artifact what a human sees
Here is the full chain in 25 lines:
package SafetyReview {
import CoffeeMachineStructure::*;
// Stakeholder — a role
part def SafetyEngineer;
// Viewpoint — the concern, expressed as a requirement
viewpoint def SafetyConcernViewpoint {
stakeholder reviewer : SafetyEngineer;
doc /* Reviewers must see all SIL-2-classified parts. */
}
// Rendering — the style
rendering def DarkInterconnection;
// View Definition — the recipe
view def SafetySIL2View :> InterconnectionView {
render rendering dark : DarkInterconnection;
filter @SafetyClassification and SafetyClassification::level == "SIL-2";
}
// View Usage — the concrete instance
view sil2Diagram : SafetySIL2View {
satisfy SafetyConcernViewpoint;
expose CoffeeMachine;
}
}
Reading top to bottom: a SafetyEngineer is the stakeholder; the SafetyConcernViewpoint captures their concern; the DarkInterconnection is a reusable rendering style; the SafetySIL2View is the recipe that filters to SIL-2 parts only and applies the dark rendering; the sil2Diagram is the concrete view a tool actually renders – it exposes the content and declares that it satisfies the viewpoint.
(Strictly, satisfy is an ordinary usage member, so the grammar admits it inside a view def body as well – the standard library’s own abstract View definition carries one. But the satisfaction claim reads most naturally on the concrete view, and that is where this book puts it.)
You do not have to use every link in the chain. Most authors start at the View end (view def + view usage) and add viewpoints + stakeholders only when traceability becomes a requirement. But when you do need traceability, every link is queryable: “which views satisfy this viewpoint”, “which viewpoints concern this stakeholder”, “what is the rendering for this view”. The chain is the trace.
How the Editor Surfaces Views
Two panels matter when you are working with views in an editor.
The Views panel lists every declared view def and view usage in the open file (or workspace). Clicking one renders the view exactly as the author specified — supertype-driven shape, exposes resolved, filters applied, rendering bound. The Views panel is the place to see views; if a view is not listed there, the model has not declared it.
The Outline panel shows the containment tree of the open file — packages, parts, ports, attributes, requirements. The Outline is not a view in the spec sense. It is a code outline, like the symbol pane in any IDE. It exists so you can navigate the model file structurally; it does not respect viewpoints, exposes, or filters. Clicking a node in the Outline reveals it in the source; it does not produce a diagram.
The two panels answer different questions:
- “What views does this model publish?” → Views panel.
- “What is in this file structurally?” → Outline panel.
If you find yourself wanting “show me this file as an interconnection diagram” with no view def in the model, the spec-faithful answer is to write a one-line scratch view and let the Views panel render it:
view scratch : InterconnectionView { expose CoffeeMachine; }
This is the same vocabulary the rest of the chapter uses — no special command, no editor mode, no separate API. Once the scratch view is in the file, it appears in the Views panel like any other view; rename it when you want to keep it. The editor may offer a “create view from selection” affordance that inserts this snippet for you, but the artifact it produces is a real view usage you can edit, name, and commit.
When to Add Metadata and Views
You do not need metadata or views from day one. They become valuable when the model is shared beyond the original author:
- Add
docearly. As soon as someone else will read your definitions, document intent. It costs almost nothing and saves review time. - Add metadata when governance starts. When the project has formal reviews, maturity gates, or safety classifications, metadata ensures that status is tracked in the model, not in a spreadsheet that drifts out of sync.
- Add views when stakeholders diverge. When different reviewers need different slices, views prevent the “here is the entire model, find what you need” problem.
If you are working alone on a small model, comments and doc may be all you need. If you are on a team with safety reviews, architecture boards, and verification milestones, metadata and views are essential governance infrastructure.
Visualizing Views and Metadata
Common Mistakes
Storing review status only in comments. A comment like // approved by safety board 2024-03-15 is invisible to tooling. If you need to query which elements are approved, that information must be metadata. Use @ReviewStatus { :>> status = "approved"; } instead.
Creating one giant view for all stakeholders. A view that exposes everything is just the model again. The value of a view is in what it leaves out. Keep views small and purpose-driven. If a stakeholder does not need to see the grinder to make their decision, do not expose it.
Duplicating model content in views. Views reference elements with expose. They do not redefine them. If you find yourself copying attribute lists or requirement text into a view, you are doing it wrong. Expose the original element and let the tool present it.
Proliferating metadata definitions. If the safety team defines SafetyLevel and the quality team defines SafetyRating with the same fields, you have a schema drift problem. Agree on shared metadata definitions early and put them in a governance package that everyone imports.
Writing a filter predicate the evaluator cannot reach. A filter expression that the runtime cannot evaluate falls through to true and admits every element (sysml-rs’s safe-default behaviour). If a view “shows more than you expected”, the most common cause is a filter expression with an unsupported operator, an unresolved reference, or a binding the evaluator does not yet handle. Stick to predicates over kind, name, id, and self’s direct attributes – those compose cleanly. If you see a stranger surface in a view, suspect a tripped safe-default before suspecting the model.
Misspelling an expose qname. expose CoffeeMachineRequirements::SaftyInterlockReq; (note the typo) does not produce an error. The qname does not resolve, the line exposes nothing, and the view comes up smaller than you expected. There is no “expose a name that does not exist” diagnostic – the line parses fine, it just has no effect. When a view comes up missing an element, check the qnames first.
Treating render as a styling hook. render rendering standard : StandardRendering; selects a RenderingDefinition. It does not set fonts, colours, or themes. If you want a view to look a particular way, the visual style is a property of the tool that consumes the view, not of the view itself. Use render to declare presentation intent (“table”, “diagram”, “matrix”) and let the tool’s visual style apply.
Expecting the Outline panel to behave like a SysML view. The Outline lists the structural contents of a file; it is not a view in the spec sense. It does not respect viewpoints, exposes, or filters, and clicking a node does not produce a diagram. If you want a diagram, the model must declare a view def and a view usage. The Views panel is where declared views live; the Outline is where you navigate the source.
Forgetting the supertype on view def. A bare view def Foo; is legal but renders as a generic projection – the most general view, GeneralView. For an interconnection, action-flow, or state-transition diagram, you must specialize from the matching standard definition: view def Foo :> InterconnectionView { ... }. There is no name-based fallback — a view called FooInterconnection without an explicit :> InterconnectionView still renders as a general view.
Putting expose inside a view def body. The grammar admits expose only in a view usage body; a view definition body takes filter and render. This one is easy to get wrong because sysml-rs currently parses an expose inside a view def without a diagnostic – the file looks fine, but the construct has no spec meaning and conformant tools reject it. “It parses” is not evidence here. Keep the recipe (supertype, filters, rendering) in the definition and the selection (expose) in the usage.
What You Have Built
The model can now describe itself and present itself.
Metadata attaches structured facts about elements – maturity, owner, safety
classification – distinct from doc, which carries prose for a human, and from
comments, which are not in the model at all.
Views turn that into something a reader can use. The chain runs
stakeholder → viewpoint → view → rendering: someone has a concern, a viewpoint
states it, a view selects the elements that answer it, and a rendering says what
form the answer takes. expose (in the usage) chooses content, filter (in the
definition) narrows it, and the definition’s supertype decides what kind of
diagram you get.
The thing most worth remembering is that a view is part of the model. It is not a diagram someone drew and saved – it is a declaration of what a particular reader needs, which is why it can be reviewed, versioned, and regenerated when the model changes.
Chapter 14 turns outward to what you did not write: the standard library, and how much of the language is defined in it.
Libraries
You have been using libraries since Chapter 2. Every time you wrote import ScalarValues::*, you reached into a model library that ships with the specification. This chapter makes that implicit knowledge explicit: what those libraries are, how they are layered, how to find things in them, and how to write one of your own.
The single most useful idea in this chapter is that the library is not a convenience bundle shipped alongside the language. A large part of what the language means is defined by library models, written in the same language you write. That is a deliberate design decision, and it is the reason this chapter is worth your time.
Why the Library Is Part of the Language
KerML is built in three syntactic layers, each adding more specific constructs on the one below (KerML §6.1):
- The Root Layer holds the most general constructs for structuring models: elements, relationships, annotations, packaging.
- The Core Layer holds the most general constructs whose semantics are based on classification.
- The Kernel Layer provides commonly needed modeling capabilities, such as associations and behavior.
Only the Core Layer is grounded directly, by interpreting it in mathematical logic. The Kernel Layer is different: its meaning comes from relating its abstract syntax constructs to model elements in the Kernel Semantic Library, which is written in KerML itself (KerML §6.1). A model you write reuses elements of that library in order to have any semantics at all, and the library models are what state the conditions under which a real system conforms to your model.
The specification is explicit about the payoff, and it is aimed squarely at you: because the library models are expressed in the same language as user models, engineers and tool builders can inspect them to understand what a model actually specifies (KerML §6.1).
So when this chapter tells you to go and read a library file, that is not a debugging tip. It is the intended way to find out what your own model means.
The Library Is Written in the Language
Here is the actual declaration of Real, from ScalarValues.kerml in the shipped standard library (the doc comments are elided):
standard library package ScalarValues {
private import Base::DataValue;
abstract datatype ScalarValue specializes DataValue;
datatype Boolean specializes ScalarValue;
datatype String specializes ScalarValue;
abstract datatype NumericalValue specializes ScalarValue;
abstract datatype Number specializes NumericalValue;
datatype Complex specializes Number;
datatype Real specializes Complex;
datatype Rational specializes Real;
datatype Integer specializes Rational;
datatype Natural specializes Integer;
datatype Positive specializes Natural;
}
That is the whole file. Eleven datatype declarations and one private import.
A model library is a collection of models meant to be reused across many user models, marked with the library keyword so tooling can recognize its contents as library elements (KerML §7.4.14). The libraries that ship with the specification go one step further and add standard, a marker reserved for the kernel and systems libraries and other recognized standard libraries (KerML §7.4.14).
This explains something you may have wondered about. Real is not a reserved word. It is not in the grammar. It is an ordinary datatype declared in an ordinary package, and you import it for exactly the same reason you import CoffeeBeans from your own package – because it lives somewhere else. Misspell it and you get an unresolved name, not a syntax error. Shadow it with a local declaration and the local one wins.
The same is true all the way down. part def Grinder works because Parts.sysml in the Systems Model Library declares abstract part def Part :> Item;, and your definition picks that up implicitly (SysML §9.1; see Chapter 12). Arithmetic works because RealFunctions.kerml declares functions named '+' and '*'.
The clearest case is ordering. When you write a succession in an action body – grind, then brew – and give it no explicit type, what you have created is a connector typed by a library association. Occurrences.kerml declares it:
assoc all HappensBefore specializes HappensLink, Without { <doc and end features> }
An ordering constraint, which certainly feels like it ought to be a grammar rule, turns out to be an association in a library file you can open and read. Nothing here is built in that could have been written down instead.
The Three Layers
The libraries are stacked in three layers, from general to specific.
| Layer | Where it is specified | What lives there |
|---|---|---|
| Kernel Model Library | KerML Clause 9 | The Semantic Library (§9.2), the Data Type Library (§9.3), the Function Library (§9.4) |
| Systems Model Library | SysML §9.2 | Parts, Items, Ports, Attributes, Actions, States, Requirements, Views, and 13 more |
| Domain Libraries | SysML §9.3 – §9.8 | Metadata, Analysis, Cause and Effect, Requirement Derivation, Geometry, Quantities and Units |
The Kernel Model Library is the foundation. The Semantic Library gives every element its meaning – Base, Occurrences, Performances, Transfers, Links, Objects and the rest (KerML §9.2). The Data Type Library holds the values: ScalarValues, Collections, VectorValues (KerML §9.3). The Function Library holds everything an expression can call (KerML §9.4). You name the Data Type Library constantly and the other two almost never.
The Systems Model Library is SysML’s own layer, and it is what turns a KerML classifier into a part, an action, or a requirement. You rarely import from it by hand, because every definition and usage you write already specializes something in it implicitly. part def Grinder picks up Parts::Part whether you ask for it or not.
Domain Libraries are the ones you import deliberately. They model concepts from domains of particular importance in systems engineering, and they are normative – available in every conformant model, not an optional add-on (SysML §9.1). Six of them ship: the Metadata Domain Library (§9.3), Analysis (§9.4), Cause and Effect (§9.5), Requirement Derivation (§9.6), Geometry (§9.7), and Quantities and Units (§9.8).
Loading is a tooling concern. In sysml-rs the kernel and systems layers load in every project with no configuration, while the domain libraries are opt-in through the
[stdlib]section ofsysml.toml. That split is the toolchain’s, not the language’s – the spec calls all six domain libraries normative. Chapter 15 covers the manifest.
What a Wildcard Import Actually Brings In
You have written import ScalarValues::* in every chapter. Now you can say precisely what it does, because you have seen the file. It brings in eleven names:
ScalarValue, Boolean, String, NumericalValue, Number, Complex, Real, Rational, Integer, Natural, Positive.
It does not bring in DataValue, even though ScalarValues imports it, because that import is written private import Base::DataValue. Which brings us to the rule that matters most when you start writing libraries of your own.
<visibility> import <QualifiedName>::*;
library package CoffeeIndustryTypes {
private import ScalarValues::*;
public import ISQ::CelsiusTemperatureValue;
enum def RoastLevel {
light;
medium;
dark;
}
item def CoffeeBeans {
attribute roast : RoastLevel;
attribute origin : String;
attribute recommendedBrewTemp : CelsiusTemperatureValue;
}
}
An import has its own visibility, independent of the visibility of the things it imports. A public import re-exports: anyone who imports CoffeeIndustryTypes also gets CelsiusTemperatureValue. A private import does not: String is usable inside CoffeeIndustryTypes but does not leak out of it.
The trap is the default. An import written with no visibility keyword at all is private (KerML §7.2.5.4). So this consumer does not compile the way you might expect:
package CoffeeMachineModel {
import CoffeeIndustryTypes::*;
import ScalarValues::*;
part def Grinder {
attribute currentBeans : CoffeeBeans;
attribute targetTemp : CelsiusTemperatureValue;
}
}
CoffeeBeans and RoastLevel arrive because they are owned public members. CelsiusTemperatureValue arrives only because the library re-exported it with public import. Real and String do not arrive through CoffeeIndustryTypes at all – hence the second import line. If you are writing a library and you want consumers to get a type you depend on, you must say public import. The standard library is meticulous about this: ISQ.sysml writes private import ScalarValues::Real; for its internal need and public import ISQBase::*; for the twelve ISO 80000 parts it exists to aggregate.
The other thing to know about wildcards is what happens on a collision. If an imported name clashes with an owned member, or with a visible name from another imported namespace, the conflicting membership is hidden – it is simply excluded from the importing namespace (KerML §7.2.5.4). There is no error. Two wildcard imports that both export Temperature will quietly cancel each other out, and the failure surfaces later as an unresolved name at the point of use. This is the concrete cost of a wildcard import, and it is why the advice from Chapter 4 hardens as a model grows: wildcard while exploring, explicit once it matters.
A false positive to expect. sysml-rs currently reports
IM006– import references unknown standard library namespace – for any single-member import from a standard library package, includingimport ScalarValues::Real;. It fires only in workspace mode (sysml inspect --workspace); the same file checked on its own is clean, and wildcard forms likeimport ISQ::*are never flagged. The check is reading the final segment of a membership import as though it named a package. The language is fine here and so is your model –import ScalarValues::Realis exactly the form Chapter 4 recommends. Do not restructure your imports to silence it.
There is a third form worth knowing, because the library documentation uses it:
import <QualifiedName>::**;
The ::** suffix makes the import recursive – it imports the namespace, its visible members, and then recurses into each imported member that is itself a namespace (KerML §7.2.5.4). Reach for it rarely. import ISQ::** is a very large hammer, as the next section makes clear.
Quantities and Units
attribute waterTemp : Real says almost nothing. Celsius, Fahrenheit, kelvin? The Quantities and Units Domain Library (SysML §9.8) exists to close that gap, and it is the most practical thing in this chapter.
Two packages do the work. ISQ gives you the quantity types – the International System of Quantities from ISO/IEC 80000. SI gives you the units. A dimensioned literal pairs a number with a unit:
<number> [ <unit> ]
package CoffeeMachineWithUnits {
import ISQ::CelsiusTemperatureValue;
import ISQ::PressureValue;
import ISQ::VolumeValue;
import ISQ::DurationValue;
import SI::*;
part def Brewer {
attribute waterTemp : CelsiusTemperatureValue;
attribute brewPressure : PressureValue = 9.0 [SI::Pa];
attribute brewTime : DurationValue = 25.0 [SI::s];
}
part def WaterTank {
attribute currentLevel : VolumeValue;
attribute maxCapacity : VolumeValue = 1.5 [SI::L];
}
}
The square brackets are not array syntax. [ is a genuine operator, declared in the library as calc def '[' { in num: Number[1]; in mRef: ScalarMeasurementReference[1]; return quantity : ScalarQuantityValue[1]; } in QuantityCalculations. It takes a number and a measurement reference and constructs a quantity value. The specification uses the same form in an accept action: accept after 30 [SI::s] (SysML §8.4.13.6).
SI::Pa, SI::s, SI::L are the unit short names. SI.sysml declares each unit with both, as in attribute <Pa> pascal : PressureUnit = N/m^2; – so SI::Pa and SI::pascal name the same thing, and the unit’s definition in terms of the base units is right there in the model.
Watch the temperature names
This is worth a paragraph because it will bite you. There is no ISQ::TemperatureValue declared anywhere. The name resolves, but only as an alias: ISQThermodynamics.sysml contains alias TemperatureValue for ThermodynamicTemperatureValue;. Thermodynamic temperature is measured in kelvin. So attribute waterTemp : ISQ::TemperatureValue = 93.0 [SI::K] describes water 180 degrees below freezing.
There are three temperature quantities, and the distinction between them is real physics rather than library clutter:
package TempOptions {
import ISQ::ThermodynamicTemperatureValue;
import ISQ::CelsiusTemperatureValue;
import ISQ::TemperatureDifferenceValue;
import SI::*;
part def Brewer {
attribute targetTemp : ThermodynamicTemperatureValue = 366.15 [SI::K];
attribute elementRise : TemperatureDifferenceValue = 6.0 [SI::'°C'];
attribute brewTemp : CelsiusTemperatureValue = 93.0 [SI::'°C_abs'];
}
}
ThermodynamicTemperatureValue (ISQBase.sysml) is absolute temperature in kelvin. CelsiusTemperatureValue (ISQThermodynamics.sysml) is the Celsius reading. TemperatureDifferenceValue is declared in ISQ.sysml itself, and its doc comment explains why it has to exist separately: a difference of 6 °C is not the same kind of quantity as a temperature of 6 °C.
The unit names repay a close look, because they are how the library encodes that difference. SI::'°C' is typed TemperatureDifferenceUnit, and it converts to kelvin with a factor of 1. SI::'°C_abs' is not a unit at all: it is typed IntervalScale, which sits under MeasurementScale rather than MeasurementUnit, and it carries an explicit zero shift against the kelvin scale.
The reason for that split is visible in the library. UnitConversion declares exactly three features – referenceUnit, conversionFactor, isExact. There is no offset anywhere in it, so unit conversion is purely multiplicative. An absolute Celsius reading needs a zero shift, so it cannot be expressed as a MeasurementUnit, and the library models it as a scale instead. Celsius is an interval scale, not a ratio scale, and the type hierarchy says so.
The 273.15 is not missing, either – it is inside the '°C_abs' declaration, as a placement of one coordinate frame relative to another:
private attribute zeroDegreeCelsiusInKelvin: ThermodynamicTemperatureValue = 273.15 [K];
attribute zeroDegreeCelsiusToKelvinShift : CoordinateFramePlacement :>> transformation {
:>> source = K; :>> origin = zeroDegreeCelsiusInKelvin;
}
That is worth noticing, because it reframes what looks like a gap. The library has not forgotten about Celsius. It has decided that a scale with a shifted origin is a coordinate frame placement, not a unit conversion – which is precisely why Celsius cannot be another derived unit in the manner of km or kPa.
Quote both names – ° is not an identifier character, so SI::'°C' needs the single quotes.
Note that
SIships no instance typedCelsiusTemperatureUnit, the type thatCelsiusTemperatureValueredefines itsmRefto. The interval scale is what you use in practice, and sysml-rs accepts it.SI.sysmlis candid about gaps of this kind in its own header: “This is a representative but not yet complete list of measurement units.” When you need a unit the library does not define, declare it in your own library package rather than waiting for one to appear.
The scale of it
Some sense of proportion, since it bears on how you import. ISQ is almost entirely an aggregator: twelve public imports, one for base quantities and eleven for the numbered ISO/IEC 80000 parts, plus the three temperature-difference declarations it owns. Those twelve packages declare 945 attribute defs between them, of which 524 are distinct *Value quantity types, plus 250 aliases. SI adds 255 units. SI also does public import ISQ::*, so import SI::* pulls every quantity type in behind the units.
That is fine – name resolution does not care about volume – but it is a lot of surface for a collision to hide in. If your model needs four quantity types, import four quantity types.
What sysml-rs actually checks. The runtime carries dimensions through expressions and reports mismatches as
UQ001. Givenattribute confused : DurationValue = brewPressure * brewTime;it reports: attribute ‘confused’ declares dimension [T] but its default value has dimension [L^-1·M·T^-1]. Two limits are worth knowing. A bare literal is not compared against its attribute’s type, soattribute brewPressure : PressureValue = 9.0 [SI::s];passes silently; the check fires on expressions and on references to other attributes. And mixing dimensions under+yields an unconstrained result rather than an error, sobrewPressure + brewTimeis accepted. Dimensional checking here catches real mistakes but is not yet a proof of dimensional soundness.
Finding Things in the Library
The question you will actually have is “does something for this already exist, and what is it called?” You do not need to guess. The library is source on disk, in libraries/standard/, split into library.kernel/, library.systems/, and library.domain/.
Two habits answer almost everything.
Grep the library. Functions are declared two different ways, so search for both. KerML uses function with no def; SysML uses calc def:
grep -rnE "(function|calc def) '?sqrt'?" libraries/standard/
library.domain/Quantities and Units/QuantityCalculations.sysml:46: calc def sqrt{ in x: ScalarQuantityValue[1]; return : ScalarQuantityValue[1]; }
library.kernel/RealFunctions.kerml:39: function sqrt{ in x: Real[1]; return : Real[1]; }
Two answers, and the signatures tell you which one applies to which argument type. The '? in that pattern matters: many library names are quoted, and a plain grep "function sqrt" would have missed nothing here but will give you false negatives elsewhere.
For a quantity type, search the units directory and search for aliases separately, or you will conclude a name does not exist when it does:
grep -rn 'TemperatureValue' 'libraries/standard/library.domain/Quantities and Units/'
Evaluate the name. sysml eval runs a single expression with no model file, which confirms in about a second that a function exists and behaves the way you think:
sysml eval 'sqrt(16.0)' # 4
sysml eval 'max(3.0, 7.5)' # 7.5
sysml eval 'round(3.5)' # 4
sysml eval 'deg(3.14159)' # 179.9998479605043
sysml eval 'size(1..3)' # 3
Two caveats on
eval. It tells you what this toolchain evaluates, which is a subset of what the library declares.StringFunctionsdeclaresLength, butsysml eval 'Length("espresso")'reportsunknown function: Length– the name is real, the evaluator has not implemented it. Andevalhas no model in scope, so anything needing a type or a feature reference has to go in a file instead. Read the library for what the language declares; useevalfor what you can run today.
One habit not to rely on: a clean parse. sysml inspect --diagnostics does not report unresolved import names, so a file full of misspelled library types comes back with zero diagnostics. Confirm names against the library, not against a green parse.
Reading a kernel library file
Open a file under library.systems/ and it looks like the SysML you have been writing all book. Open one under library.kernel/ and the first line may not. The kernel libraries are written in KerML, one layer below SysML, and they use declaration keywords SysML models never need. Here is enough to stop being surprised by them.
| KerML | What it declares | Real use |
|---|---|---|
struct | A structure. SysML’s part def and item def sit on this. | abstract struct Object specializes Occurrence |
assoc | An association – a relationship as a classifier. | assoc all HappensBefore specializes HappensLink, Without |
assoc struct | An association that is also a structure, so the link itself has features. | abstract assoc struct LinkObject specializes Link, Object |
function | A function. This is the KerML spelling; SysML writes calc def. | function sqrt{ in x: Real[1]; return : Real[1]; } |
behavior | A behavior. SysML’s action def sits on this. | abstract behavior Performance specializes Occurrence |
inv | An invariant – a constraint that must hold. | inv { flattenedSize == size(elements) } |
bool | A boolean expression, not a boolean type. | bool guard[*] subsets enclosedPerformances; |
bool is the one most likely to mislead, so it is worth being explicit: the grammar production is BooleanExpression, and bool introduces an expression that yields a boolean. It is not a shorthand for the Boolean datatype.
You will also meet disjoint, which asserts that two types can have no common instances:
abstract behavior Performance specializes Occurrence disjoint from Object { <body> }
That says a performance is never an object – something happening is not something existing. It reads oddly the first time and then becomes one of the more useful things in the semantic library.
Three further keywords exist for writing a relationship as a standalone, separately named declaration rather than inline:
subclassifier X specializes Y,disjoining <name> disjoint X from Y, andinverting <name> inverse X of Y. They are rare – across all 36 kernel library files,subclassifieris used once and the other two never. If you meet them, they are the same relationships in a different shape.
That is the whole surprise. The kernel libraries are not a different language; they are the layer SysML’s own keywords are defined on top of, which is exactly what KerML §6.1 says they are.
The Function Library
The libraries are not only a type vocabulary. They also declare every function an expression can call:
package TankMath {
import ScalarValues::*;
part def WaterTank {
attribute maxCapacity : Real default = 1500.0;
attribute currentLevel : Real := 0.0;
attribute headroom : Real = max(0.0, maxCapacity - currentLevel);
attribute fillFraction : Real = currentLevel / maxCapacity;
}
}
max is not built into the language. It is declared in RealFunctions, and it is in scope because the Function Library loads with every project. Here is the useful subset, with the package each name actually comes from:
| Group | Package | Names |
|---|---|---|
| Real arithmetic | RealFunctions | abs, min, max, sqrt, floor, round, sum, product, re, im, arg |
| Cross-numeric | NumericalFunctions | isZero, isUnit, abs, min, max, sum, product, sum0, product1 |
| Conversions | RealFunctions, IntegerFunctions, RationalFunctions | ToInteger, ToRational, ToReal, ToNatural, ToString |
| Trigonometric | TrigFunctions | sin, cos, tan, cot, arcsin, arccos, arctan, deg, rad |
| Strings | StringFunctions | Length, Substring, ToString |
| Sequences | SequenceFunctions | size, isEmpty, notEmpty, includes, excludes, union, intersection, including, excluding, subsequence, head, tail, last |
| Collections | CollectionFunctions | size, isEmpty, notEmpty, contains, containsAll, head, tail, last |
| Iteration and filtering | ControlFunctions | select, selectOne, collect, reject, reduce, forAll, exists, allTrue, anyTrue, minimize, maximize |
| Occurrences | OccurrenceFunctions | isDuring, create, destroy, addNew, addNewAt |
| Quantities | QuantityCalculations | ConvertQuantity, ToDimensionOneValue, plus dimension-aware abs, min, max, sqrt, sum, product |
Two things to keep straight.
First, arithmetic, ordering and equality are written as operators, not as function calls. Exponentiation is ** or ^; ordering is < > <= >=; inequality is !=. There is no pow, no lessThan, no notEquals – reach for the operator. Appendix A has the full operator table.
But – and this is the interesting part – those operators are library functions. RealFunctions declares function '**' and function '<'. BaseFunctions declares function '==', function 'istype', function 'as' and function 'meta'. ControlFunctions declares function 'if', function 'and', function 'or', function 'implies'. The names are quoted because they are not identifiers, and that is the only reason a grep for function istype comes back empty while function 'istype' finds it. So the operators are not a separate mechanism bolted onto the grammar; they are library functions with special syntax, which is why QuantityCalculations can declare its own '+' and give addition a dimension-aware meaning for quantity values.
Second, that table is a subset, not an inventory. Each number type has its own package (IntegerFunctions, NaturalFunctions, RationalFunctions, ComplexFunctions, VectorFunctions), and there are more for booleans, data values and scalars. Look for a library name before writing your own calc def.
Writing Your Own Library Package
Everything above applies to libraries you write. The declaration takes one extra keyword:
library package <Name> { ... }
library package CoffeeIndustryTypes {
private import ScalarValues::*;
public import ISQ::CelsiusTemperatureValue;
enum def RoastLevel {
light;
medium;
dark;
}
item def CoffeeBeans {
attribute roast : RoastLevel;
attribute origin : String;
attribute recommendedBrewTemp : CelsiusTemperatureValue;
}
}
library marks the package as reusable vocabulary rather than part of one particular design, which lets tooling treat everything inside it as a library element (KerML §7.4.14). Define domain vocabulary once here, import it everywhere else, and the definitions cannot drift apart.
Three things to get right when you write one:
- Re-export what your consumers need.
public importfor anything that appears in a signature they will use;private importfor your internal dependencies. Getting this backwards produces a library whose types nobody can name. - Keep the export surface small. Your consumers will wildcard-import you. Every public name is a potential silent collision.
- Do not write
standard.standard library packageis reserved for the kernel and systems libraries and other recognized standard libraries (KerML §7.4.14). It is not a quality marker for your own work.
Libraries the Runtime Recognizes
A handful of library packages are structural entry points: specialize from them and a simulation runtime knows what your model means without further configuration. They span two layers, which is worth keeping straight when you go looking for them on disk.
From the Analysis Domain Library (SysML §9.4):
| Package | What it provides |
|---|---|
StateSpaceRepresentation | The ODE pattern: calc def GetDerivative and calc def GetOutput to subtype, action def ContinuousStateSpaceDynamics to specialize (see Chapter 16) |
TradeStudies | TradeStudy, EvaluationFunction, MinimizeObjective, MaximizeObjective |
SampledFunctions | SampledFunction, SamplePair, and the Domain / Range / Sample / Interpolate calculations |
AnalysisTooling | Exactly two declarations, both metadata: metadata def ToolExecution and metadata def ToolVariable, for tying a model to an external analysis tool |
From the Kernel Model Library (KerML §9.2):
| Package | What it provides |
|---|---|
Occurrences | Occurrence, Life, and the snapshots / startShot / endShot structure – anything with temporal extent |
Clocks | Clock, BasicClock, and the universalClock feature |
SpatialFrames | SpatialFrame, CartesianSpatialFrame, and the defaultFrame feature |
Performances | The Performance and Evaluation hierarchy behind actions and expressions |
Typing for dynamics. In sysml-rs, when the simulation runtime sees connections between ISQ-typed ports it classifies each port’s role in a bond graph – effort, flow, displacement, momentum – and synthesizes the conservation laws from the dimensions. Nobody writes the balance equations by hand; the runtime reads them off the types. The rule of thumb is to type any attribute that participates in dynamics with an
ISQquantity rather thanReal. Appendix E has the details.
What You Have Built
You now know what the standard library is, and why it is not optional. It is roughly ninety files of KerML and SysML source, layered as kernel, systems, and domain, and a large share of the language’s meaning is defined there rather than in the grammar – which is why the specification expects you to read it. You can say exactly what import ScalarValues::* brings into scope and why import without a visibility keyword does not re-export. You can attach real dimensions to the coffee machine’s attributes with ISQ and SI, you know which of the three temperature types you actually want, and you know how far your tool’s dimensional checking goes. You can open a kernel file without being stopped by assoc or inv. And when you need a function, you have two ways to find out whether it already exists rather than writing a calc def the library already has.
The remaining question is where the library comes from – which libraries a project loads, how versions get pinned, and how you ship your own library package so somebody else can depend on it. That is Chapter 15.
Projects and Workspaces
Two Questions That Sound Alike
In Chapter 4, you organized model elements into packages and made names visible across package boundaries with import. In Chapter 14, you saw that a large share of the language’s meaning lives in standard libraries your model draws on. Both chapters kept quiet about a practical question: when you write import BeverageTypes::*, how does a tool know where BeverageTypes lives – which file, which directory, which version?
The honest answer is that these are two different questions, and only one of them belongs to SysML v2.
- “What does this name mean here?” is a language question. KerML defines name resolution precisely: resolution starts in the local namespace and searches outward through containing namespaces (KerML §7.2.5.1), and
importmakes another namespace’s visible members available under shorter names (KerML §7.2.5.4). Every conformant tool must answer this question the same way. - “Which files and projects should be loaded so those names exist at all?” is a tooling question. The specification defines resolution over the elements a tool has loaded; it does not say how the tool decides what to load. Manifests, dependency declarations, version pins, lock files, and workspace layouts are each tool’s own answer, and they differ between tools.
Keep the two apart and this chapter is short. An import is part of your model and travels with it. A dependency declaration is part of your project setup and does not.
import Is Language; a Dependency Is Not
Here is the boundary in one example. The model side is SysML v2 and means the same thing in every conformant tool:
package BeverageTypes {
item def Water;
item def CoffeeBeans;
}
package CoffeeParts {
private import BeverageTypes::*;
part def WaterTank {
item water : Water;
}
}
The import says: inside CoffeeParts, the visible members of BeverageTypes may be used by their unqualified names. It says nothing about disk layout. BeverageTypes could be in the same file, another file, another project, or an archive fetched from a server – the import is equally correct in all four cases, and equally meaningless if no loaded element provides the name.
The project side, by contrast, lives in a tool manifest. In sysml-rs it looks like this:
[dependencies]
beverage-types = { path = "../beverage-types" }
That line is not SysML v2. It is an instruction to one particular toolchain about where to find another project so that its packages become loadable. Another SysML v2 tool will express the same intent with a different file, or a repository configuration, or an IDE project – and your model’s import lines do not change when it does.
import BeverageTypes::* | beverage-types = { path = ... } | |
|---|---|---|
| Lives in | your .sysml model source | a tool manifest |
| Defined by | KerML §7.2.5.4 | the tool |
| Answers | what a name means in a namespace | where a project’s sources come from |
| Portable across tools | yes | no |
The practical rule: if removing a line changes what your model means, it is language. If removing it changes what your tool can find, it is tooling.
Projects, Commits, and Interchange
The language stops at name resolution, but OMG does standardize two things above the level of a single file, and both are worth knowing at concept level.
Projects and commits. The SysML v2 specification suite includes a standard API for model repositories, organized around a small set of concepts: a project is a top-level container for a model, a branch is a named line of development within it, and a commit is an immutable snapshot of the model at a point in time. These are interchange notions – they let two tools talk about “the same model at the same version” without sharing an implementation. Appendix C covers the API surface itself.
Interchange artifacts. As Chapter 1 noted, model interchange is normative: a project travels as a .kpar archive, and the textual files inside it use the .sysml extension. A .kpar bundles the project’s source files with a .project.json manifest that records the project’s identity and the libraries and projects it uses. These are artifacts tools produce and consume so that models can cross tool boundaries; in day-to-day modelling you author .sysml files and let your toolchain generate the interchange form when you publish.
That is the whole standardized story: names resolve by KerML’s rules, projects and commits identify model versions, and .kpar carries a project between tools. Everything else – how dependencies are declared, how versions are pinned and cached, how several projects are grouped into a workspace – is toolchain territory.
Working with Projects in sysml-rs
sysml-rs tooling – documented on the portal. Project and workspace mechanics in sysml-rs are product documentation, not language, and their canonical home is the sysml-rs documentation portal. Start there for the current behaviour; the pages below are kept up to date with the tool.
- Imports vs. dependencies – the boundary above, from the tool’s side.
- The
sysml.tomlmanifest – project identity, editions, and standard-library selection.- Declaring dependencies – path, git, KPAR, and registry dependencies;
sysml add/sysml remove.- Lock files and the cache –
sysml.lock, reproducible resolution, and what to commit.- Workspaces – grouping several projects and how the tool actually discovers and loads them.
- KPAR packaging – building a
.kpararchive for interchange withsysml package.
What Travels and What Does Not
Every construct in the previous fourteen chapters is SysML v2 and travels with your model. The way this toolchain names, versions, and locates projects does not travel – another SysML v2 tool answers the same needs differently. What does carry across tools is the concept layer: KerML’s name-resolution rules, the project/commit vocabulary, and the .kpar interchange form. Learn the concepts here; look up the mechanics on the portal for whichever tool you are using.
Chapter 16 is the last one, and it asks what it means for a model like this to run – what a run is, how a state machine advances, and where a continuous ODE fits alongside discrete behaviour.
Running Your Model
The previous chapters taught you how to write a SysML v2 model: parts, actions, states, expressions, constraints, requirements, verification cases, metadata, and views. Everything so far has been structural – it describes what your coffee machine is. Even the state machine in Chapter 8 was a description, not an execution. Even the constraints in Chapter 9 were inert.
This chapter is about the other half. A SysML v2 model can also run. The same attribute waterTemp : Real that was a static description in Chapter 5 becomes a value that changes over time. The same state heating that was a static node in Chapter 8 becomes a state the machine actually enters and leaves. The same constraint that was a static obligation in Chapter 9 becomes a verdict that flips when a value moves.
This is not a separate language. There is no “simulation SysML” to learn. The constructs you already know describe the dynamic behaviour. What this chapter teaches is how to read your own model once it starts running, and the handful of authoring patterns that make a model run cleanly.
A note on scope. The concepts here are language: the run-as-occurrence framing, the state-space pattern, binding override, and the verdict stream are things any SysML v2 author should understand. The mechanics – which solver picks up your ODE, what the tool’s run surface looks like, how a trace is encoded, which commands to type – are sysml-rs’s runtime, and their canonical documentation is the sysml-rs portal: the runtime and CLI workflows. Blockquotes in this chapter mark the boundary.
16.A What It Means for a SysML v2 Model to Run
Start with the distinction that everything else depends on:
the model a file (or workspace) of declarations. Has a tree shape,
cross-references, types. Has no time and no current value.
a run of it one trajectory through that model. Has a clock, a current
value for every observable, a history, and a current state
for every state machine.
The model is not the run. The model is what you wrote. A run is one execution of it. One model has many possible runs.
The language already has vocabulary for this, in KerML rather than SysML. A definition classifies; the thing classified is an occurrence, and clause 9.2.4.2.13 of the KerML specification puts it plainly: “An Occurrence is Anything that happens over time and space (the four physical dimensions). Occurrences can be portions of another Occurrence within time and space, including slices in time, leading to snapshots that take zero time.”
That sentence is the whole abstraction. A run is an occurrence. A tick is a snapshot of it. Asking “what is waterTemp?” of the model is a category error; you ask it of a snapshot.
Authors coming from static tools blur the two. They edit the model when they want to change one run; they talk about the answer when there are many. Hold the distinction and three later patterns become obvious: overriding a binding (§16.C) changes the run, not the model; verification (§16.D) checks a run; a Monte Carlo sweep is N runs over one model.
A run you can read
Here is a discrete run, end to end. The model is a three-state machine whose states write to shared attributes on entry – nothing in it is written for simulation; entry; then idle;, accept startGrind, and entry action are the Chapter 8 constructs:
state def GrinderController {
doc /* Controls the burr grinder.
* Writes: grindWeight, grinderRunning
*/
entry; then idle;
state idle {
entry action { grinderRunning = 0; }
}
state grinding {
entry action { grinderRunning = 1; grindWeight = 0; }
}
state dosed {
entry action { grinderRunning = 0; grindWeight = 18.5; }
}
transition idle_to_grinding first idle accept startGrind then grinding;
transition grinding_to_dosed first grinding accept doseReached then dosed;
transition dosed_to_idle first dosed accept clearDose then idle;
}
Drive a run of it with the event sequence startGrind, doseReached, clearDose and read what comes back as a run rather than as a diagram. The run starts in idle – that is the entry; then idle; succession resolved. At each point, the set of available transitions is the set enabled from the state the run is currently in – not from the state definition, from the run. startGrind moves it to grinding, whose entry action sets grinderRunning to 1 and grindWeight to 0; doseReached moves it to dosed, where grindWeight becomes 18.5; clearDose returns it to idle. That pair of numbers – grindWeight was 0 at one tick and 18.5 at the next – exists nowhere in the model. It belongs to this run. The run is a reading of the Chapter 8 declarations.
Whatever tool you use, a run offers roughly four operations, and everything later in this chapter is built on them: step it forward one tick; run it until a horizon, a guard, or you stop it; reset it, returning time to t=0 and every observable to its declared initial value; and fork it, so two runs share a past and diverge from here.
sysml-rs extends the spec here. The specification defines the elements of a model and their semantics. It does not prescribe a run loop or a session abstraction, and it defines no command-line interface. Step, run, reset and fork are sysml-rs’s session API; another conformant tool will do these things under other names, or not run models at all. How to start, drive, and inspect a run in sysml-rs is documented on the portal: the runtime and CLI workflows.
16.B ODE-Bearing Models – The Language Story
The biggest reason a model has to run rather than just be analyzed is that it contains differential equations. Boiler water does not jump from 22 degrees to 92; it warms along a thermal curve. Conductor current does not appear instantaneously; it rises along an inductor curve.
SysML v2 has a standard-library answer, and it needs no metadata and no tool-specific annotation. The shape is:
part def <Thing> {
attribute <parameters> // constants of the run
out attribute <stateVar> default <x0> // integrated over time
action def <Dynamics> :> ContinuousStateSpaceDynamics {
calc def <D> :> GetDerivative { return d<stateVar>dt = <rhs>; }
calc def <O> :> GetOutput { return <name> = <expr>; }
event occurrence <e> : ZeroCrossingEventDef;
}
}
Here is that shape as a real, running file – a vessel heated at a constant rate past boiling:
private import StateSpaceRepresentation::*;
part def HeatedVessel {
// Parameters
attribute heaterRate : Real default 5.0;
attribute boilingPoint : Real default 100.0;
// ODE state variable
out attribute temperature : Real default 20.0;
action def HeatingDynamics :> ContinuousStateSpaceDynamics {
// Derivative: dT/dt = heaterRate
calc def TemperatureDerivative :> GetDerivative {
return dTdt = heaterRate;
}
// Algebraic output: overshoot above boiling point
calc def OvershootOutput :> GetOutput {
return overshoot = temperature - boilingPoint;
}
event occurrence boilingReached : ZeroCrossingEventDef;
}
}
Four things in that file carry the meaning, and each has a name:
- An
out attributeon the part is a state variable: a value the run integrates rather than one you assign. Itsdefaultis the initial condition. - A
calc defspecializingGetDerivativeis a state equation. Its body is the right-hand side of dx/dt = f(x, u). - A
calc defspecializingGetOutputis an output equation: an algebraic function of the state, computed each tick, never integrated. y = g(x, u). - An
action defspecializingContinuousStateSpaceDynamicsis the envelope that holds them and marks the part’s behaviour as continuous.
All four come from StateSpaceRepresentation, a standard library package, and the specification devotes clause 9.4.4 to it. Its overview is worth quoting for one warning it contains: state-space representation describes “a set of state variables whose evolution by a state equation (note that this is a different conception of ‘state’ than used in the behavioral state modeling constructs described in 7.18)”. A state variable and a state machine state are different things. temperature is the former; heating is the latter. This chapter uses both, and they meet in the hybrid pattern below.
ZeroCrossingEventDef is also from that package. Clause 9.4.4.2 explains why it exists: solvers, “especially variable-step ones, need to identify such points for precise integration”, and an implementation “may notify zero-crossings with these event occurrences”. Declaring one says this run has a boundary the integrator should locate, not fire this callback.
Tool note: name your return variable after the state. sysml-rs matches a derivative to the state variable it integrates by name, so write
return dwaterTempdtforwaterTemp, and pick state names where none is a substring of another. The matching rule and its edge cases are documented on the portal under the runtime.
The coffee-machine boiler
Cast in the running example, with the constraints from Chapter 9 alongside:
package BoilerHybrid {
private import ScalarValues::*;
private import StateSpaceRepresentation::*;
part def Boiler {
attribute heaterPower : Real default 1500.0;
attribute ambientTemp : Real default 20.0;
attribute thermalMass : Real default 1500.0;
attribute heatLossCoeff : Real default 4.5;
attribute readyTemp : Real default 92.0;
out attribute waterTemp : Real default 22.0;
action def BoilerFieldDynamics :> ContinuousStateSpaceDynamics {
calc def WaterTempDerivative :> GetDerivative {
return dwaterTempdt =
(heaterPower - heatLossCoeff * (waterTemp - ambientTemp))
/ thermalMass;
}
calc def HeadroomOutput :> GetOutput {
return headroom = readyTemp - waterTemp;
}
event occurrence readyReached : ZeroCrossingEventDef;
}
}
constraint def HeaterRated {
heaterPower > 0.0 and heaterPower <= 2000.0
}
constraint def PositiveThermalMass {
thermalMass > 0.0
}
constraint def TempInRange {
waterTemp >= 0.0 and waterTemp <= 105.0
}
}
You can read the derivative’s value at any state by hand, which is the cheapest way to sanity-check a right-hand side before running anything. Cold, at 22 degrees, (1500.0 - 4.5 * (22.0 - 20.0)) / 1500.0 is 0.994 – near full rate. At temperature, 92 degrees, (1500.0 - 4.5 * (92.0 - 20.0)) / 1500.0 is 0.784 – losses biting. The derivative falls as the water warms, which is what a first-order thermal lag should do. That check costs nothing and catches sign errors and misplaced parentheses, and any expression evaluator will do it – yours, or the one described in the portal’s CLI workflows.
When the ODE depends on a state machine
Real systems are hybrid: an ODE describes the continuous evolution, and a state machine selects which regime applies. A boiler is the textbook case. The state variable waterTemp is one variable; whether the heater is driving it is a state-machine question.
You compose the two by writing them next to each other and letting the guards read the state variable:
state def BoilerMode {
in attribute waterTemp : Real;
in attribute readyTemp : Real;
in attribute standbyTemp : Real;
state idle;
state heating;
entry; then heating;
transition heating_to_idle
first heating
accept when waterTemp >= readyTemp
then idle;
transition idle_to_heating
first idle
accept when waterTemp <= standbyTemp
then heating;
}
Two constructs are doing the work. The in attribute declarations are how the state machine names the continuous values it depends on; they are bound to the part’s state variables of the same name when the model is assembled. And accept when <expression> is a change trigger: the transition fires when the expression becomes true, rather than on a received event. A change trigger over a state variable is exactly the zero crossing that ZeroCrossingEventDef announces.
The same pattern scales. A full-size hybrid model – a reciprocating pump, say – gives its cycle state machine in attributes for each continuous value it needs (stroke, velocity, exposure), and every cycle transition is a located crossing of one of them:
transition compress_to_discharge
first compress
accept when velocity < 0.0
then discharge;
Its dynamics envelope supplies one :> GetDerivative calc per state variable and a handful of :> GetOutput calcs for the shared algebraic chain. The distribution of labour is the general one: derivatives say how the state moves, outputs say what it means, guards say when the regime changes.
sysml-rs extends the spec here. Given a
calc def :> GetDerivativeinside anaction def :> ContinuousStateSpaceDynamics, sysml-rs auto-detects the ODE and picks an integrator; when a change trigger mentions a state variable it locates the crossing with sub-tick precision rather than at the next discrete step. Neither is authored. Solver selection and how continuous runs are driven are documented on the portal under the runtime.
A note on what’s gone. Older sysml-rs material and example models declared ODEs with
@ToolVariable { derivative = "..." }and@ToolExecution { toolName = "builtin:ode-rk45" }. All of that is removed. Those two metadata definitions are real – they are the whole content of the standard library’sAnalysisToolingpackage, and they mean “dispatch this action to an external tool” – but they are not how you write an ODE. If you find a model that still uses them for one, rewrite it with the state-space pattern above:AnalysisToolingmakes a model tool-specific,StateSpaceRepresentationdoes not.
16.C One Model, Many Runs: Overriding a Binding
You have a boiler model. Someone asks: what if the water came in at 110 degrees, could that happen? You would like to answer without editing the file, because editing means committing to a value, re-checking everything, and telling everyone who depended on the old one.
What you want is to override a binding for one run:
same model + binding B1 -> run 1 -> answer 1
same model + binding B2 -> run 2 -> answer 2
The language framing is already familiar. Every calc def is an equation. Every in binding is an input substitution. Every :>> redefinition is a substitution you wrote into the source. What is new at run time is only that the substitution does not require editing the source.
With the boiler model from §16.B, a run at declared values satisfies all three constraints: HeaterRated, PositiveThermalMass, and TempInRange all pass. Re-evaluate the same file with the single binding waterTemp = 110.0 overridden, and TempInRange – waterTemp >= 0.0 and waterTemp <= 105.0 – fails while the other two still pass. Two answers, one model, nothing edited. That is the whole idea, and it scales in three directions you may already use:
- What-if analysis overrides one binding and re-evaluates interactively.
- Trade studies sweep a binding across a range and re-evaluate at each point.
- Monte Carlo samples a binding from a distribution and re-evaluates across N draws.
All three are one override repeated. Think of every calc as an equation, every binding as an input, and every override as a substitution, and it does not matter whether the override comes from a UI knob, an API call, or a sweep script.
sysml-rs extends the spec here. The override mechanism – a flag on the constraint checker, an interactive equations workbench that lists the model’s equations and exposes their bindings as editable fields, and the sweep APIs – is tooling, and all of its entry points go through the same path, so they agree. See the portal’s runtime and CLI workflows pages.
16.D Sensing What Happened – Verdicts and Traces
The run finished. The boiler heated, the mode flipped, a constraint changed verdict. The question that matters is rarely “what was the value at tick 35” but “why”.
Verdicts as live observations
Chapter 9 introduced assert constraint, which produces a verdict at evaluation time. Chapter 10 aggregated verdicts into verification outcomes. Both treated a verdict as a discrete artifact: you evaluated, you got an answer.
In a run it is not discrete. A constraint over a state variable is re-decided as the run advances, so one constraint becomes a stream of verdicts, each carrying the operand values that were live when it fired. The TempInRange failure in §16.C is one sample from that stream: the sample where waterTemp was 110.0.
The verdict is four-valued, not two. The specification’s VerdictKind (clause 9.2.17.2.2) enumerates exactly pass, fail, inconclusive and error, and inconclusive is not failure: it means the constraint could not be decided. That happens whenever an operand has no value, which is common mid-authoring:
constraint def ReadyTempSane {
readyTemp <= 100.0
}
constraint def PumpPressureOk {
pumpPressure >= 9.0 // pumpPressure is not declared anywhere
}
Evaluate these and the honest reading is “one passed, none failed, one inconclusive”: ReadyTempSane passes, and PumpPressureOk cannot be decided because pumpPressure names nothing. A tool that scored the second as a failure would send you hunting for a physics bug that is really a missing declaration; one that scored it as a pass would be lying. When you consume verdicts programmatically, insist on the four-valued kind, not a boolean.
sysml-rs extends the spec here. That an asserted constraint yields a four-valued verdict is spec (Chapter 10). How often it is evaluated is not. sysml-rs re-evaluates every asserted constraint on every tick of a run, because constraints are cheap and a dense verdict stream makes a better trace; another tool might evaluate once at the end, or only when asked. The verdict wire format and the verification commands are on the portal under the runtime and CLI workflows.
From a verdict to a verification case
A verification case rolls the stream up into one verdict for a requirement. The idiom is the one from Chapter 10, and it reads the run’s values as free names:
part def Boiler {
out attribute waterTemp : Real default 22.0;
}
requirement def ReachesBrewTemp {
require constraint atTemperature {
waterTemp >= 92.0
}
}
verification def BrewTempCase {
subject boiler;
objective brewTempObjective {
verify requirement brewTempCheck : ReachesBrewTemp;
}
}
Run against the declared initial condition, the boiler has not heated yet, so BrewTempCase fails. Override the binding to waterTemp = 95.0, as in §16.C, and the same case passes. Note that waterTemp in atTemperature is a free name resolved from the run, not a feature of the requirement. That is what makes the same requirement reusable across runs – and it is why an un-run model reports the constraint as inconclusive (waterTemp has no value yet) rather than issuing a verdict. Inconclusive, again, is the right answer there.
Causal trace
Every value change in a run has a cause. A constraint flipped because a state variable crossed a threshold; the variable crossed because the integrator stepped a derivative; the derivative was non-zero because a transition switched the active regime; the transition fired because a change trigger became true. Those are causal links, and a runtime can record them and walk them backwards.
A useful cause is often a *non-*event. Guard a brew transition on a value another subsystem writes:
transition standby_to_brewing
first standby
accept startBrew
if machineReady > 0
then brewing;
Drive the brew controller with startBrew on its own and it does not move: machineReady is still 0, because nothing has run the boiler. “The brew did not start because machineReady was 0” is the answer you want, and it is only available because the guard names a shared attribute rather than testing an anonymous expression.
Which is the whole language-level lesson: the trace is only as good as the identifiers in your model. A trace that says “heating_to_idle fired because waterTemp reached readyTemp” is readable; “t_3 changed attr_17” is not, and no tooling can repair it. The cost of skipping a name is visible immediately. Write your constraints in the anonymous nested form, constraint { boilerTemp < 100 }, and the verdicts come back with nothing in the name column:
[PASS] : machineReady > 0
[FAIL] : waterPressure >= 8 and waterPressure <= 10
Ten constraints, ten blank labels, three failures you now have to locate by reading expressions. Name your transitions, name your constraints, name the return variable of every calc. That is not decoration.
Breakpoints
A breakpoint stops a run at an event so you can look at it. The events worth stopping on are the ones the language already names:
- entering or leaving a state
- a transition firing
- an action being invoked
- a constraint flipping verdict
- a state variable crossing a threshold
You do not author breakpoints inside the model; they are decorations on a run, set from outside, and another tool reading the same model will not see them. So the authoring work that makes breakpoints useful is the work that makes traces useful: clear state names, named transitions, named constraints.
sysml-rs extends the spec here. The causal recorder, its query shape, and the breakpoint variants are runtime features, not specification. They are documented on the portal under the runtime.
How These Patterns Compose
The four sections stand alone but interlock:
- A model with an ODE (§16.B) needs a run (§16.A) to evolve at all.
- A run can be re-asked with a different binding (§16.C), which is how you get from one trajectory to a trade study.
- A run emits a verdict stream (§16.D), and a causal trace walks back from any verdict to its cause.
- A verification case (Chapter 10) rolls the stream into one answer about a requirement.
The boiler exercises all four. WaterTempDerivative evolves waterTemp; BoilerMode selects whether the heater is driving it; overriding heaterPower answers “what if the element were bigger”; TempInRange says whether the water stayed safe; BrewTempCase says whether the requirement held. You did not learn a new language for any of it – you wrote out attribute, state, calc def ... :> GetDerivative, accept when, and constraint def. The same declarations that were a static description in earlier chapters are a running system here. The runtime only reads them.
Where the Mechanics Live
sysml-rs tooling – documented on the portal. Everything behind the blockquotes in this chapter – sessions and the run loop, solver auto-selection, binding overrides and sweeps, the verdict wire format, causal-trace queries, breakpoints, and the command surface that drives them – is product documentation, and its canonical home is the sysml-rs portal: the runtime and CLI workflows.
If you are writing a model you intend to run, you should not need to reach for those. Occurrence and snapshot, state variable and state, derivative and output, binding and override, verdict stream and trace – that vocabulary is enough.
Appendix A: Syntax Quick Reference
This appendix is a compact index of SysML v2 syntax forms. For each construct family you will find the canonical declaration pattern, a one-line description of purpose, and a pointer to the chapter that explains the semantics in full. Use it as a quick lookup when you know what you want to write but cannot remember the exact keyword order.
Each code block is a minimal but valid syntax template. Angle-bracket placeholders like <Name> are things you supply. Items in [...] are optional. Items separated by | are alternatives.
Operators and Relationship Punctuation
Every relationship in SysML v2 is written as a punctuation token after a name. This table covers all the tokens you will encounter in declarations.
| Token | Long form | Meaning |
|---|---|---|
: | defined by | Type a feature: names the classifier the feature conforms to |
:> | specializes / subsets | Specialize a type or subset a feature |
:>> | redefines | Redefine (override) an inherited feature |
::> | references | Reference-subsetting: a non-owning reference to another feature |
=> | crosses | Cross-subsetting: coordinates the two ends of a binary connection definition |
~ | (none) | Conjugated port typing: port p : ~P types a port by the conjugate of P (flips in/out) |
= | (feature value) | Bind a feature value (default or fixed) |
:= | (initial value) | Set an initial value that may change at runtime |
default = | (default value) | Set a default value that specializations may override |
default := | (default initial) | Set a default initial value |
The long forms are the SysML spellings. KerML spells : as typed by and has a general conjugates keyword on type declarations; neither is a SysML keyword. Appendix G lists the full symbol/keyword pairs with the metamodel class each one creates.
Examples
// Typing: the feature waterTemp is of type Real
attribute waterTemp : Real;
// Subsetting: grinder subsets parts (narrows the inherited feature)
part grinder : Grinder :> parts;
// Redefining: espressoBrewTemp narrows and renames waterTemp
attribute espressoBrewTemp :>> waterTemp;
// Reference-subsetting: a non-owning reference to the primary sensor
ref primarySensor ::> sensors;
// Conjugation: ~WaterPort flips the direction of all port features
port def WaterPort; // conjugate is ~WaterPort, generated automatically
// Fixed value
attribute maxTemp : Real = 96.0;
// Initial value (may change)
attribute currentTemp : Real := 20.0;
// Default value (overridable)
attribute targetTemp : Real default = 93.0;
Multiplicity
Multiplicity constrains how many instances of a feature may exist. Write it in brackets after the feature name (or after its type).
| Syntax | Meaning |
|---|---|
[1] | Exactly one (default for most features) |
[0..1] | Zero or one (optional) |
[1..*] | One or more |
[*] | Zero or more (unbounded) |
[2] | Exactly two |
[1..4] | Between one and four |
// Two sensors, exactly
part sensors : CupSensor [2];
// Optional steam wand
part steamWand : SteamWand [0..1];
// Any number of logged events
item events : LogEvent [*];
Feature Modifiers
These keywords add properties to a feature declaration. Most are prefixes that appear before the feature keyword; ordered and nonunique are the exception – they follow the multiplicity.
| Modifier | Meaning |
|---|---|
abstract | Feature or definition has no direct instances; must be specialized |
in | Input direction (for action parameters) |
out | Output direction (for action parameters) |
inout | Bidirectional (for action parameters) |
ref | Non-owning reference usage (does not own the referenced element) |
derived | Value computed from other features; not directly settable |
constant | Value stays the same for the entire existence of the featuring instance (KerML files spell it const) |
variation | Marks a definition or usage as a variation point whose variant members are the allowed choices |
ordered | Sequence order of feature values is significant (written after the multiplicity) |
nonunique | Duplicate values are permitted (written after the multiplicity) |
var | Value may vary over time (KerML files only) |
end | Marks a connector end feature |
// Abstract part: every concrete machine must specialize this
abstract part def Machine;
// Input parameter
in attribute targetPressure : Real;
// Derived attribute computed from sub-parts
derived attribute totalMass : Real;
// Ordered list of brew steps (`ordered` follows the multiplicity)
ref action brewSteps : BrewStep [*] ordered;
Visibility
Visibility controls whether a member is accessible outside its owning namespace.
| Keyword | Meaning |
|---|---|
public | Accessible everywhere (default for package members) |
private | Accessible only within the owning namespace |
protected | Inherited by specializations of the owning definition or usage, but not visible outside; on other namespaces (e.g. package members) it is equivalent to private |
package CoffeeMachineDomain {
public part def CoffeeMachine { ... } // visible outside
private part def InternalBoard { ... } // hidden from importers
}
Documentation and Comments
// Line note -- discarded by the parser
//* Block note, also discarded.
It runs to the closing marker, so always close it. */
/* A bare block comment IS model data: it becomes a Comment element
whose body is this text */
doc /* Documentation attached to the owning element as model data */
comment about CoffeeMachine
/* Standalone annotating comment -- can target any element */
// and //* are hidden terminals (hidden(WS, ML_NOTE, SL_NOTE)) – the parser throws them away, so nothing downstream can see them. /* ... */ is not hidden: alone it becomes a Comment element, and after doc it becomes documentation on the owning element.
Mind the closing marker: ML_NOTE is defined as '//*' -> '*/', so a //* note runs until the next */ anywhere later in the file. Leave one unclosed and it silently swallows everything up to the next block comment. Appendix H covers doc versus //* and when to reach for each.
Group 1: Packages and Namespaces
Packages organize model elements into named scopes.
// Plain package
package <Name> {
<members>
}
// Library package (marks reusable standard content)
library package <Name> {
<members>
}
// Standard library package (normative standard libraries only)
standard library package <Name> {
<members>
}
Grammar: SysML.xtext lines 183-208
See: Chapter 4: Packages and Imports
Group 2: Imports and Aliases
Imports bring names from other namespaces into scope.
// Import a single named member
import <Package>::<Name>;
// Import all public members of a package
import <Package>::*;
// Import all members recursively (including nested packages)
import <Package>::*::**;
// Private import (imported names stay private to this namespace)
private import <Package>::*;
// Alias: give a new local name to an existing element
alias <LocalName> for <Package>::<OriginalName>;
Grammar: SysML.xtext lines 234-298
See: Chapter 4: Packages and Imports
Group 3: Definitions and Usages
Every major element kind has a paired definition (reusable type) and usage (instance within a context). The pattern is uniform across all element families.
// General pattern
<keyword> def <Name> [specialization] {
<members>
}
<keyword> <name> [: <Type>] [specialization] [= <value>];
<keyword> <name> [: <Type>] [specialization] {
<members>
}
The definition declares a reusable classifier. The usage declares an occurrence of that classifier within an owning definition.
Grammar: SysML.xtext lines 303-330; KerML.xtext lines 460-558
See: Chapter 3: Definitions and Usages
Group 4: Typing and Specialization
These four relationships control how types relate to each other.
// Typing (feature conforms to a classifier)
part grinder : Grinder;
// Specialization (subtype extends a supertype)
part def EspressoMachine :> CoffeeMachine { ... }
// Subsetting (a feature narrows an inherited feature)
part grinder : Grinder :> components;
// Redefinition (override an inherited feature with a new name or type)
part espressoGrinder :>> grinder;
// Conjugation (type a port by the conjugate of a port definition)
port waterReturn : ~WaterPort;
Grammar: KerML.xtext lines 332-626
See: Chapter 12: Specialization and Redefinition
Group 5: Structural Constructs
Attribute
Holds a data value (scalar, string, quantity, etc.).
// Definition
attribute def <Name> [: <SuperType>] { <members> }
// Usage
attribute <name> : <Type> [multiplicity] [= <value>];
attribute def Temperature :> Real;
attribute waterTemp : Temperature = 93.0;
Grammar: SysML.xtext lines 739-749
Enumeration
A type whose values are a fixed set of named literals.
enum def <Name> {
<literal>;
<literal>;
}
enum def DrinkSize {
small;
medium;
large;
}
Grammar: SysML.xtext lines 764-787
Item
A thing that can flow through the system (material, energy, signal, data).
// Definition
item def <Name> [: <SuperType>] { <members> }
// Usage
item <name> : <Type> [multiplicity];
item def Water;
item def BrewedCoffee;
item waterSupply : Water [1];
Grammar: SysML.xtext lines 910-916
See: Chapter 5: Structure
Part
A physical or logical component that composes a system.
// Definition
part def <Name> [:> <SuperType>] { <members> }
// Usage
part <name> : <Type> [multiplicity];
part def CoffeeMachine {
part grinder : Grinder [1];
part brewer : Brewer [1];
}
part myCoffeeMachine : CoffeeMachine;
Grammar: SysML.xtext lines 932-938
See: Chapter 5: Structure
Port
An interaction point through which a part communicates with its environment.
// Definition
port def <Name> { <port features> }
// Usage
port <name> : <PortType>;
port <name> : ~<PortType>; // conjugated: reverses in/out
port def WaterPort {
out item waterOut : Water;
}
part def WaterTank {
port waterOut : WaterPort;
}
part def Brewer {
port waterInlet : ~WaterPort; // conjugated: receives water
}
Grammar: SysML.xtext lines 952-985
See: Chapter 5: Structure
Connection
A link between two or more parts or port ends.
// Definition
connection def <Name> [:> <SuperType>] { <members> }
// Usage: named connection
connection <name> connect <source> to <target>;
// Usage: anonymous inline connection
connect <source> to <target>;
// Binding connector (equates two features)
bind <feature1> = <feature2>;
connection def WaterLine :> BinaryConnection;
connect waterTank.waterOut to brewer.waterInlet;
bind brewer.targetTemp = controlPanel.setTemp;
Grammar: SysML.xtext lines 1015-1080
See: Chapter 6: Connections and Interfaces
Interface
A typed connection between ports, specifying a protocol.
// Definition
interface def <Name> {
end <portName> : <PortType>;
end <portName> : <PortType>;
}
// Usage
interface <name> : <InterfaceType> connect <port1> to <port2>;
interface <name> connect <port1> to <port2>; // untyped
interface def WaterInterface {
end supply : WaterPort;
end demand : ~WaterPort;
}
interface waterLine : WaterInterface connect
waterTank.waterOut to brewer.waterInlet;
Grammar: SysML.xtext lines 1102-1181
See: Chapter 6: Connections and Interfaces
Allocation
A directed mapping from one element to another (logical-to-physical, function-to-component).
// Definition
allocation def <Name> [:> <SuperType>];
// Usage
allocate <source> to <target>;
allocation <name> allocate <source> to <target>;
allocate brewCycle to brewer;
allocation heatingAlloc allocate heatAction to brewer.heatingElement;
Grammar: SysML.xtext lines 1195-1216
See: Chapter 6: Connections and Interfaces
Group 6: Behavioral Constructs
Action
A unit of behavior with inputs, outputs, and internal control flow.
// Definition
action def <Name> {
in <param> : <Type>;
out <result> : <Type>;
<action body>
}
// Usage
action <name> : <ActionType>;
// Perform (reference an action that already exists elsewhere)
perform action <name> : <ActionType>;
// Succession (ordering between two steps)
first <actionName>
then <nextAction>;
action def BrewCycle {
in beanType : BeanType;
out coffee : BrewedCoffee;
action grind : GrindBeans;
action heat : HeatWater;
action extract : Extract;
first grind then heat;
first heat then extract;
}
action brewCycle : BrewCycle;
Grammar: SysML.xtext lines 1352-1518
See: Chapter 7: Actions
Action Nodes (Control Flow)
Built-in action node types for structured control flow inside an action body.
// Conditional
if <condition> {
<true branch>
} else {
<false branch>
}
// While loop
while <condition> {
<body>
}
// Loop-until variant (`loop` takes no condition of its own)
loop {
<body>
} until <condition>;
// For loop
for <var> : <Type> in <collection> {
<body>
}
// Send a message (`to` names the receiver, `via` the sending port; both optional)
send <payloadExpr> to <receiver>;
send <payloadExpr> via <senderPort>;
send <payloadExpr> via <senderPort> to <receiver>;
// Accept a message or trigger
accept <payload> : <Type>;
accept <payload> : <Type> via <port>;
accept at <time>;
accept after <duration>;
accept when <change-condition>;
// Assignment
assign <target> := <expr>;
// Control nodes (name optional)
fork <name>; // split into concurrent branches
join <name>; // wait for all branches
merge <name>; // accept whichever branch arrived
// A decision node is followed by its branches, one succession per line
decide <name>;
if <guardExpr> then <targetStep>;
else <targetStep>;
// Abort the enclosing action
terminate;
Grammar: SysML.xtext lines 1437-1734
See: Chapter 7: Actions
State
A named state in a state machine; may contain entry, do, and exit sub-actions.
// Definition
state def <Name> {
entry; // or: entry <action>;
do; // or: do <action>;
exit; // or: exit <action>;
transition <Name> first <state>
accept <trigger>
if <guard>
do <effect>
then <target>;
}
// Usage
state <name> : <StateType>;
// Exhibit (reference a state that exists elsewhere)
exhibit state <name> : <StateType>;
exhibit state <name> { <states and transitions> }
exhibit <stateUsageRef>;
// Parallel states
state <name> parallel {
<region1>;
<region2>;
}
state def MachineStates {
state idle;
state brewing;
state cleaning;
transition T1 first idle
accept StartBrewEvent
if cupDetected
then brewing;
transition T2 first brewing
accept BrewCompleteEvent
then idle;
}
state machineStates : MachineStates;
Grammar: SysML.xtext lines 1735-1923
See: Chapter 8: States
Transition
A directed arc between two states, with optional trigger, guard, and effect.
transition [<Name>] first <source>
[accept <trigger> [via <port>]]
[if <guard-expression>]
[do <effect-action>]
then <target>;
transition T1 first idle
accept StartBrewEvent
if cupSensor.cupDetected
then brewing;
Grammar: SysML.xtext lines 1849-1877
See: Chapter 8: States
Succession
Ordering relationship between two occurrences (temporal “happens before”).
// Inline within an action or state body
first <occurrence1>
then <occurrence2>;
// Explicit succession
succession <name> first <occurrence1> then <occurrence2>;
// Guarded target -- follows a decision node, one line per branch
if <guardExpr> then <targetStep>;
// Default target -- taken when no guard matched (note: no `then`)
else <targetStep>;
Grammar: SysML.xtext lines 1028-1033
See: Chapter 7: Actions
Flow
A directed transfer of items between feature ends.
// Definition
flow def <Name> [:> <SuperType>];
// Usage: anonymous inline flow
flow of <ItemType> from <source> to <target>;
// Named flow usage
flow <name> of <ItemType> from <source> to <target>;
// Succession flow (ordered flow across a time boundary)
succession flow of <ItemType> from <source> to <target>;
flow def WaterFlow;
flow of Water from waterTank.waterOut to brewer.waterInlet;
Grammar: SysML.xtext lines 1230-1285
See: Chapter 6: Connections and Interfaces
Calculation
A function-like behavior that returns a value. Used for expressions and analyses.
// Definition
calc def <Name> {
in <param> : <Type>;
return <result> : <ReturnType>;
<result expression>
}
// Usage
calc <name> : <CalcType>;
calc def BrewTime {
in volume : Real;
in pressure : Real;
return t : Real = volume / pressure * 60.0;
}
Grammar: SysML.xtext lines 1937-1974
See: Chapter 9: Expressions and Constraints
Group 7: Analytical Constructs
Constraint
A Boolean predicate that must hold for the model to be valid.
// Definition
constraint def <Name> {
<boolean expression>
}
// Usage
constraint <name> : <ConstraintType>;
// Inline constraint
constraint { <boolean expression> }
// Assert a constraint holds
assert constraint <name>;
// Assert a constraint does not hold
assert not constraint <name>;
constraint def BrewTempInRange {
brewer.waterTemp >= 90.0 and brewer.waterTemp <= 96.0
}
assert constraint BrewTempInRange;
Grammar: SysML.xtext lines 1988-2012
See: Chapter 9: Expressions and Constraints
Concern
A stakeholder interest modeled as a requirement-like predicate.
// Definition
concern def <Name> { <requirement body> }
// Usage
concern <name> : <ConcernType>;
Grammar: SysML.xtext lines 2150-2160
See: Chapter 10: Requirements and Verification
Group 8: Requirements
Requirements capture what a system must do or be.
Requirement
// Definition
requirement def <Name> {
subject <subjectName> : <SubjectType>;
assume constraint { <assumption expression> }
require constraint { <requirement expression> }
frame <concern>;
stakeholder <name>;
}
// Usage
requirement <name> : <RequirementType>;
requirement def BrewTimeRequirement {
subject machine : CoffeeMachine;
require constraint { machine.brewTime <= 60.0 }
}
requirement brewTime : BrewTimeRequirement;
Grammar: SysML.xtext lines 2026-2110
See: Chapter 10: Requirements and Verification
Satisfy
Declares that a subject satisfies a requirement.
// Reference an existing requirement usage
satisfy <requirementUsageRef> by <subject>;
// Declare the satisfied requirement here, typed by a requirement definition
satisfy requirement [<name>] : <RequirementDef> by <subject>;
assert satisfy <requirementUsageRef> by <subject>;
assert not satisfy <requirementUsageRef> by <subject>;
satisfy requirement : BrewTimeReq by myCoffeeMachine;
Grammar: SysML.xtext lines 2112-2120
See: Chapter 10: Requirements and Verification
Group 9: Cases
Cases are structured analyses with a subject, objective, and result.
Analysis Case
Captures a system analysis (e.g., performance, safety).
analysis def <Name> {
subject <name> : <SubjectType>;
objective { <requirement body> }
<action body items>
return <result> : <ResultType>;
}
analysis <name> : <AnalysisType>;
Grammar: SysML.xtext lines 2227-2233
See: Chapter 10: Requirements and Verification
Verification Case
Documents how a requirement is verified.
verification def <Name> {
subject <name> : <SubjectType>;
objective { verify requirement <ReqName>; }
}
verification <name> : <VerificationType>;
Grammar: SysML.xtext lines 2249-2255
See: Chapter 10: Requirements and Verification
Use Case
Documents system usage by actors.
use case def <Name> {
subject <name> : <SubjectType>;
actor <actorName> : <ActorType>;
objective { <requirement body> }
}
use case <name> : <UseCaseType>;
// Include another use case inline
include use case <name>;
Grammar: SysML.xtext lines 2287-2301
See: Chapter 10: Requirements and Verification
Group 10: Views and Viewpoints
Views select and frame content for a specific audience.
Viewpoint
Defines the concerns and stakeholders a view must address.
viewpoint def <Name> {
stakeholder <name> : <StakeholderType>;
frame <concern>;
require constraint { <criteria> }
}
viewpoint <name> : <ViewpointType>;
Grammar: SysML.xtext lines 2399-2405
See: Chapter 13: Metadata and Views
View
Projects selected model content through a rendering.
view def <Name> :> <SupertypeView> {
filter <BooleanExpression>;
render rendering <name> : <RenderingDef>;
}
view <name> : <ViewType> {
expose <element>; // member expose
expose <Namespace>::*; // namespace expose
filter <BooleanExpression>;
render <renderingUsageRef>;
}
// Anonymous view usage — no def required
view <name> { expose <element>; }
view def StructuralView :> InterconnectionView {
filter @SysML::PartUsage;
render rendering asTree : Views::GraphicalRendering;
}
view structuralView : StructuralView {
expose CoffeeMachineDomain::*;
}
view scratch { expose CoffeeMachine; } // anonymous
expose is legal only in a view usage body; a view definition body admits filter and render (ViewDefinitionBodyItem has no expose alternative). sysml-rs currently fails to flag an expose inside a view def – do not read that as acceptance.
The eight standard view definitions (package StandardViewDefinitions) are: GeneralView, InterconnectionView, ActionFlowView, StateTransitionView, SequenceView, GeometryView, GridView, BrowserView. There is no standard UseCaseView or RequirementView; a requirement view is written as a GeneralView specialization with a requirement-metaclass filter.
filter expressions are Boolean and are evaluated once per exposed element. The spec’s own idiom is a classification test against a metaclass or metadata definition – filter @SysML::PartUsage;, filter @Safety and Safety::isMandatory;. The self, kind, name, and id bindings used elsewhere in this book are a sysml-rs evaluator extension, not spec language. Multiple filter clauses on the same view compose as logical AND.
Grammar: SysML.xtext lines 2315-2383
See: Chapter 13: Metadata and Views
Rendering
Defines how a view’s contents should be displayed.
rendering def <Name> { <rendering body> }
rendering <name> : <RenderingType>;
Grammar: SysML.xtext lines 2417-2427
Group 11: Metadata
Metadata annotates any model element with typed, queryable data.
// Definition (a metaclass)
metadata def <Name> {
attribute <field> : <Type>;
}
// Usage: attach metadata to an element
@<MetadataType> { <field> = <value>; }
// Using the metadata keyword explicitly
metadata <name> : <MetadataType> about <element>;
// Prefix form: a user-defined keyword before the declaration it annotates
#<MetadataType>
<annotated element>
metadata def ReviewStatus {
attribute reviewer : String;
attribute approved : Boolean;
}
part def CoffeeMachine {
// Without an `about` clause, a metadata usage annotates its
// containing element -- so this attaches to CoffeeMachine
@ReviewStatus { reviewer = "Alice"; approved = true; }
}
Grammar: SysML.xtext lines 115-177
See: Chapter 13: Metadata and Views
Group 12: Expressions
Expressions compute values. They appear in constraints, feature values, calculations, and guard conditions.
Arithmetic and Logic Operators
| Category | Operators |
|---|---|
| Arithmetic | + - * / % ** ^ |
| Comparison | < > <= >= |
| Equality | == != === !== |
| Logical (eager – both operands evaluated) | & | xor |
| Logical (short-circuit) | and or implies |
| Null coalescing | ?? |
| Unary | + - ~ not |
| Ternary conditional | if <cond> ? <then> else <else> |
| Range | <low>..<high> |
Type Testing and Casting
// Test whether a value is an instance of a type
<expr> hastype <Type>
<expr> istype <Type>
<expr> @ <Type>
// Cast to a type
<expr> as <Type>
// Metaclass test / cast
<expr> @@ <MetaType>
<expr> meta <MetaType>
// Extent: all instances of a type
all <Type>
Collection and Feature Navigation
// Dot notation: navigate to an owned feature
brewer.waterTemp
// Feature chaining: navigate through a chain
machine.brewer.waterTemp
// Sequence expression (parenthesized, comma-separated)
(a, b, c)
// Range (also usable in expressions)
1..10
// Indexing (1-based)
<sequence>#(n)
// Sequence operators (arrow form; the per-element rule is a body expression)
<sequence>->select {in <x>; <predicate over x>}
<sequence>->collect {in <x>; <expr over x>}
<sequence>->reject {in <x>; <predicate over x>}
<sequence>->forAll {in <x>; <predicate over x>}
<sequence>->exists {in <x>; <predicate over x>}
// Aggregators (argument-list form -- the sequence is the argument)
<sequence>->size()
<sequence>->isEmpty()
<sequence>->notEmpty()
<sequence>->sum()
<sequence>->product()
// min and max are two-argument functions, so a sequence is folded with ->reduce
<sequence>->reduce min
<sequence>->reduce max
Body Expression (Inline Function)
// Body expression -- the inline function passed to ->select, ->collect, ->forAll, ...
{in <param>; <expression>}
{in <param> : <Type>; <expression>}
Grammar: KerMLExpressions.xtext (full file, 579 lines)
See: Chapter 9: Expressions and Constraints
Occurrence Modifiers
Occurrences (parts, items, actions, states) can carry temporal modifiers.
| Keyword | Meaning |
|---|---|
individual | Represents exactly one real-world individual (not a reusable type) |
snapshot | A portion that captures the state at an instant in time |
timeslice | A portion that spans a time interval |
individual part myEspressoMachine : EspressoMachine;
snapshot part machineAtStartup : CoffeeMachine;
timeslice part machineInOperation : CoffeeMachine;
Grammar: SysML.xtext lines 801-860
The Definition/Usage Pattern (Summary)
Every major construct family in SysML v2 follows this paired pattern:
| Definition keyword | Usage keyword | What it models |
|---|---|---|
attribute def | attribute | Scalar data value |
enum def | enum | Enumerated type |
item def | item | Flowing thing |
part def | part | Physical/logical component |
port def | port | Interaction point |
connection def | connection | Link between ends |
interface def | interface | Typed port-to-port link |
allocation def | allocation | Logical-to-physical mapping |
flow def | flow | Item transfer |
action def | action | Unit of behavior |
state def | state | State machine state |
calc def | calc | Returning computation |
constraint def | constraint | Boolean predicate |
requirement def | requirement | System obligation |
concern def | concern | Stakeholder interest |
case def | case | Generic structured analysis |
analysis def | analysis | System analysis case |
verification def | verification | Verification case |
use case def | use case | Actor-system interaction |
viewpoint def | viewpoint | Stakeholder concern framing |
view def | view | Projected model content |
rendering def | rendering | Display format |
metadata def | metadata or @ | Model annotation |
connect <a> to <b> is not the connection usage keyword – it is the clause that introduces the ends, and stands alone only as the short form of an unnamed connection. The same pattern holds for allocate (usage keyword allocation) and bind (usage keyword binding). See Appendix G for the metamodel class behind each pair.
Cross-References
- Chapter 1: Why Textual SysML v2 introduces the definition/usage split and explains why the language is text-first.
- Chapter 3: Definitions and Usages explains ownership and membership semantics.
- Chapter 4: Packages and Imports explains packages and imports in depth.
- Chapter 5: Structure explains parts, items, attributes, and ports.
- Chapter 6: Connections and Interfaces explains connections, interfaces, allocations, and flows.
- Chapter 7: Actions explains actions, action nodes, and successions.
- Chapter 8: States explains state machines and transitions.
- Chapter 9: Expressions and Constraints explains calculations, constraints, and expressions.
- Chapter 10: Requirements and Verification explains requirements, concerns, cases, and satisfaction.
- Chapter 12: Specialization and Redefinition explains every relationship operator in the table above.
- Chapter 13: Metadata and Views explains the metadata system, views, and viewpoints.
Source Anchors
- Grammar (primary):
references/sysmlv2/SysML-v2-Pilot-Implementation/org.omg.sysml.xtext/src/org/omg/sysml/xtext/SysML.xtext(2,433 lines) - Grammar (KerML base):
references/sysmlv2/SysML-v2-Pilot-Implementation/org.omg.kerml.xtext/src/org/omg/kerml/xtext/KerML.xtext - Grammar (expressions):
references/sysmlv2/SysML-v2-Pilot-Implementation/org.omg.kerml.expressions.xtext/src/org/omg/kerml/expressions/xtext/KerMLExpressions.xtext - Metamodel vocabulary:
references/sysmlv2/SysML-vocab.ttl(182 SysML types) - KerML vocabulary:
references/sysmlv2/Kerml-Vocab.ttl(84 KerML types) - Corpus examples:
sysml-spec-tests/corpus/advent/(56 files) - Coffee Machine examples:
examples/coffee-machine/(12 files)
Appendix B: Migration from SysML v1
If you have existing SysML v1 models, this appendix maps the v1 concepts to their v2 equivalents and provides a practical migration approach.
Concept Mapping
SysML v2 replaces SysML v1’s diagram-centric approach with a textual, element-centric model. Many v1 concepts have direct v2 equivalents, but the framing is different.
Structural Concepts
| SysML v1 | SysML v2 | Notes |
|---|---|---|
| Block | part def | Blocks become part definitions. The keyword changed, but the intent is the same. |
| Block property | part usage | A property typed by a Block becomes a typed part usage. |
| Value Type | attribute def | Value types become attribute definitions. |
| Value Property | attribute usage | Properties typed by a value type become attribute usages. |
| Flow Port | port def / port | Flow ports become port definitions with directional items. |
| Full Port | port def / port | Full ports become port definitions. The in/out direction model is more explicit. |
| Item Flow | flow on connection | Item flows are now part of connection modeling. |
| Internal Block Diagram | Composite structure in part def | No separate diagram type – structure is expressed inline. |
| Block Definition Diagram | part def declarations | Definitions are written directly in text. |
Behavioral Concepts
| SysML v1 | SysML v2 | Notes |
|---|---|---|
| Activity | action def | Activities become action definitions. |
| Activity Partition | Ownership or allocation | Partitions become structural ownership or explicit allocations. |
| State Machine | state def with state usages | State machines are modeled as state definitions with transitions. |
| State | state def / state | States become state definitions (reusable) or state usages (in context). |
| Transition | transition ... from ... to ... | Transitions name source, target, guard, and trigger explicitly. |
| Signal | item def | Signals become item definitions that flow through ports. |
Requirements and Traceability
| SysML v1 | SysML v2 | Notes |
|---|---|---|
| Requirement | requirement def | Requirements become first-class model elements with doc text. |
| Satisfy relationship | satisfy | Direct equivalent. Applied inside the satisfying element. |
| Verify relationship | verify inside verification def | Verification cases explicitly link to requirements. |
| Derive relationship | Specialization (:>) | Derived requirements specialize their parent. |
| Copy relationship | No direct equivalent | Use specialization or imports instead. |
| Requirement Diagram | Package with requirements | No separate diagram – requirements live in packages. |
Organizational Concepts
| SysML v1 | SysML v2 | Notes |
|---|---|---|
| Package | package | Direct equivalent. |
| Model | Top-level package | No separate Model element. Packages serve the same role. |
| Profile | metadata def | Profiles become metadata definitions applied with @. |
| Stereotype | metadata def | Stereotypes become metadata definitions. |
| Tagged Value | attribute in metadata def | Tagged values become attributes on metadata definitions. |
| View | view / viewpoint def | Views and viewpoints are now explicit model elements. |
Semantic Caveats
The mapping tables above are one-to-one enough to be useful, but three of the mappings hide a change in meaning. These are the ones teams get wrong.
A v1 Block splits into two concepts
In v1 a Block played two roles with no syntactic distinction. On a BDD it stood for a classifier – a reusable type. On an IBD it appeared as a property – an occurrence of another Block owned by a parent. Tools inferred the role from context.
v2 makes the distinction mandatory and syntactic. Every v1 Block becomes one of:
part defif it was used as a type – something instantiated more than once, referenced from elsewhere, or specialized.- a
partusage (nodef) if it was used as a slot – one occurrence inside one owning definition.
So a v1 Block: CoffeeMachine with part properties brewer : Brewer and grinder : Grinder becomes two kinds of element:
part def Brewer { }
part def Grinder { }
part def CoffeeMachine {
part brewer : Brewer;
part grinder : Grinder;
}
Brewer and Grinder are definitions; brewer and grinder are usages of them. The test when you are unsure: can this element be typed by, specialized by, or referenced from somewhere else? If yes, it needs def.
FlowPort direction becomes conjugation
A v1 FlowPort carried direction as a flag – in, out, or inout – and you matched an out port to an in port when connecting.
v2 uses conjugation instead. You write one port def from one side’s perspective and take its conjugate with ~, which reverses every flow direction inside it. Two ports are connector-compatible when one is the conjugate of the other:
port def WaterOutlet {
out item water : Water;
}
part def WaterTank {
port supply : WaterOutlet; // supplies water
}
part def Brewer {
port inlet : ~WaterOutlet; // receives water (conjugate)
}
out item water becomes in item water in ~WaterOutlet. You did not write a second port def; the language derived it.
For migration: for every pair of v1 FlowPorts you used to connect, write one port def and apply ~ at the other end. Writing symmetric WaterInlet/WaterOutlet pairs is the v1 pattern translated literally, not the v2 idiom.
Stereotypes become first-class metadata
In v1, extending the language meant a UML Profile: a Profile package holding Stereotype elements that extended base metaclasses, applied through ProfileApplication, with tagged values as owned Property elements. It worked, but profiles coupled models together, tagged values were stringly-typed in practice, and cross-tool compatibility was uneven.
v2 replaces the whole mechanism with metadata def – an ordinary element declared in a package alongside part def, carrying typed attributes, applied with @. No profile file, no ProfileApplication, no metaclass extension:
metadata def Safety {
attribute level : String;
attribute standard : String;
}
@Safety { :>> level = "SIL-2"; :>> standard = "IEC-61508"; }
part def Brewer {
attribute waterTemp : Real;
}
Three consequences. The annotations live in your package structure, so they import, specialize, and query like anything else. The attributes are strongly typed – including quantity types from ISQ – where v1 tagged values defaulted to String even when a number was meant. And :>> is just the redefinition syntax you already use everywhere else; there is no separate tagged-value syntax.
One thing metadata does not do, which profiles attempted: it does not extend the metamodel. Stacking @ annotations adds independent annotations to an element; it does not create new kinds of element or new structural relationships. If you wrote profiles to introduce constructs that behaved like SysML built-ins, those have to be re-expressed with standard v2 mechanisms.
Diagram Types to Constructs
v1 divided modeling across nine diagram types, each a separate artifact with its own notation. v2 has no diagram types – the model is one textual artifact – but the concerns each diagram addressed still exist as language constructs. This table maps the concern; the view-preset table further down maps how you would render it.
| v1 diagram type | Abbr. | v2 constructs covering the same concern |
|---|---|---|
| Block Definition Diagram | BDD | part def, attribute, port def, interface def, item def, connection def at package scope |
| Internal Block Diagram | IBD | part usages, connect, flow, port usages inside a part def body |
| Requirement Diagram | REQ | requirement def, requirement usages, satisfy, verify, nested decomposition; refinement is expressed by specialization (:>), not a refine keyword |
| Activity Diagram | ACT | action def, action usages, first/then successions, flow, fork, join, decide, merge, send, accept |
| State Machine Diagram | STM | state def, state usages, transition, accept, if guard, do effect, entry/do/exit sub-actions, exhibit state |
| Parametric Diagram | PAR | constraint def, constraint usages, assume constraint, require constraint |
| Sequence Diagram | SD | message usages plus successions between action performers. Partial: message is real surface syntax but produces a FlowUsage – there is no Message metaclass and no interaction def form (see Appendix G) |
| Use Case Diagram | UC | use case def, use case usages, actor, subject, include use case |
| Package Diagram | PKG | package, import, alias; public / private visibility |
What replaces the BDD/IBD boundary
The BDD/IBD split was the most operationally significant boundary in v1: the BDD defined types, the IBD defined the internal architecture of one Block. In v2 both live in one part def body:
part def CoffeeMachine {
// BDD-style: typed part usages naming sub-components
part grinder : Grinder;
part brewer : Brewer;
part waterTank : WaterTank;
// IBD-style: connectivity between those parts' ports
connect waterTank.waterOut to brewer.waterInlet;
flow of Water from waterTank.waterOut to brewer.waterInlet;
}
No file boundary, no diagram boundary, and no synchronization problem between the two.
What replaces the REQ diagram
A v1 Requirements Diagram showed Requirement-stereotyped Classes, containment decomposition, DeriveReqt arrows, and satisfy/verify dependencies. These become body members and relationships on requirement def:
requirement def BrewSafetyReqs {
doc /* The brewer shall not operate unsafely. */
subject machine : CoffeeMachine;
// nested decomposition (v1 containment)
requirement temperatureReq : WaterTemperatureReq;
requirement cupReq : CupPresenceReq;
}
// v1 satisfy dependency -> v2 satisfy
part myCoffeeMachine : CoffeeMachine {
satisfy requirement : BrewSafetyReqs;
}
Note the : in satisfy requirement : BrewSafetyReqs. The name slot after requirement declares a requirement usage; the type annotation is what binds it to BrewSafetyReqs. Writing satisfy requirement BrewSafetyReqs; parses, but it declares an untyped usage that happens to share the name and links to nothing.
What replaces the PAR diagram
A v1 Parametric Diagram showed ConstraintBlock instances with parameters wired together by binding connectors. In v2 the constraint usage and its bindings are written inline:
constraint def TemperatureInRange {
in attribute temp : Real;
in attribute minTemp : Real;
in attribute maxTemp : Real;
constraint { temp >= minTemp and temp <= maxTemp }
}
part def Brewer {
attribute waterTemp : Real;
constraint brewTempOk : TemperatureInRange {
:>> temp = waterTemp;
:>> minTemp = 90.0;
:>> maxTemp = 96.0;
}
}
The v1 binding connectors become :>> redefinitions on the constraint usage. Same structure, inline text.
Migration Strategy
Migration works best in phases. Do not try to convert everything at once.
Phase 1: Structure First
Start with your block definitions and their properties. Convert blocks to part def, value types to attribute def, and block properties to typed usages. This gives you a structural baseline in v2 that you can check immediately.
// v1: Block "Grinder" with value property "grindSize: Real"
// v2:
part def Grinder {
attribute grindSize : Real;
}
Phase 2: Requirements and Traceability
Migrate your requirements next. Convert each v1 requirement into a requirement def with a short ID and doc text. Add satisfy links to the structural elements from Phase 1.
requirement def <'REQ-001'> BrewTempReq {
doc /* Extraction temperature shall remain between 90 and 96 C. */
}
part def CoffeeMachine {
satisfy requirement : BrewTempReq;
}
This gives you traceable requirements early, before you tackle behavior.
Phase 3: Behavior
Convert activities to action def and state machines to state def. The biggest change is that v2 uses textual control flow (first...then, fork, join, decide, merge) instead of diagram-based arrows. Guards and triggers on transitions are now written inline.
Phase 4: Governance and Views
Add metadata definitions to replace profiles and stereotypes. Create viewpoints and views to replace v1’s diagram-based stakeholder communication.
The v1 diagram zoo collapses to a small set of v2 view definitions, often combined with a sysml-rs-supplied preset (in parentheses):
| v1 Diagram | v2 View Definition |
|---|---|
| Block Definition Diagram | GeneralView with a definition/usage filter (BDD preset) |
| Internal Block Diagram | InterconnectionView (IBD preset) |
| Parametric Diagram | InterconnectionView + binding overlay (Parametric preset) |
| Activity Diagram | ActionFlowView |
| State Machine Diagram | StateTransitionView |
| Sequence Diagram | SequenceView |
| Use Case Diagram | GeneralView with a use-case filter (no standard UseCaseView exists) |
| Requirement Diagram | GeneralView with a requirement-metaclass filter |
| Package Diagram | BrowserView (or Package preset) |
The eight standard view definitions (GeneralView, InterconnectionView, ActionFlowView, StateTransitionView, SequenceView, GeometryView, GridView, BrowserView) are spec, defined in the standard library package StandardViewDefinitions; the parenthesised preset names are sysml-rs’s preset registry. There is no standard use-case or requirement view definition – both are written as GeneralView specializations with a filter, which is the recipe the library’s own GeneralView documentation prescribes. Either form is enough to replace the v1 diagram. See Chapter 13.
Key Mindset Shifts
From diagrams to text. In v1, the diagram was the primary artifact. In v2, the text is the source of truth and diagrams are generated views. This changes your workflow: you edit text files, review diffs, and run checks – not drag boxes on a canvas.
From implicit to explicit. v1 often relied on diagram layout to convey meaning. v2 makes everything explicit in text: ownership, direction, typing, sequencing. This feels verbose at first but pays off in reviewability and automation.
From copy to reuse. v1 models often duplicated similar blocks across diagrams. v2’s definition/usage pattern makes reuse natural: define once, use everywhere. Resist the urge to copy-paste definitions.
From monolithic to modular. v1 models often lived in one large project file. v2 models naturally split across files and packages, with imports making dependencies visible. Plan your package structure early.
Common Migration Pitfalls
Direct syntax translation without cleanup. Migration is an opportunity to improve model quality. Do not just translate v1 ambiguity into v2 ambiguity – tighten definitions, add types, and make traceability explicit.
Postponing verification links. In v1, verification was often managed outside the model. In v2, verification cases are model elements. Add them during migration, not after.
Mixing conventions. If part of your team uses v1 habits (untyped usages, missing imports) while another part uses v2 patterns, the model becomes inconsistent. Set v2 conventions early and enforce them in review.
Appendix C. The Systems Modeling API
Alongside the textual notation this book teaches, the SysML v2 specification suite defines a second interchange surface: the Systems Modeling API and Services specification, a standard REST/HTTP API for programmatic access to model data. Where the textual notation is how people write and exchange models, the API is how tools do – a requirements-management system pulling coverage data, a CI job comparing two versions of a model, a dashboard tracking element counts across a programme, all without any of them parsing anyone’s source files.
This appendix explains the API as the OMG specification defines it: the concepts, why they are shaped the way they are, and what any conformant implementation gives you. It is a reference companion to the modelling chapters – you do not need any of it to write a SysML v2 model. Come here when you are ready to connect a model to your broader engineering toolchain.
Why a standard API exists
The textual notation answers “what does this model say?”; the API answers “how do tools agree on which model, at which version, says it?”. Interchange needs an identity story as much as it needs a syntax. The API specification supplies that story with four concepts:
| Concept | Description |
|---|---|
| Project | A top-level container. Every model lives inside a project. |
| Branch | A named line of development within a project (similar to git). |
| Commit | An immutable snapshot of the model at a point in time. |
| Element | Any SysML element: parts, requirements, actions, connections, … |
Two tools that share nothing but this vocabulary can still talk about “the same model at the same version”. A commit is immutable, so any query pinned to a project and a commit is reproducible: the same request returns the same answer next month, regardless of what the model has become since. That is the property every downstream artifact – a coverage report, an exported diagram, an audit trail – should be built on.
On top of these concepts the specification defines resource-oriented services: listing and creating projects, walking a project’s branches and commits, retrieving and storing elements at a commit, navigating ownership and relationships, and a query language (PrimitiveConstraint / CompoundConstraint objects) for filtering elements server-side. The element payloads are JSON, governed by a published JSON Schema, so the shape of a PartUsage on the wire is as standardised as its textual syntax.
Where the OMG definition lives
The normative materials ship in the spec distribution:
references/sysmlv2/OpenAPI.json– the REST endpoints, request/response schemas, and error codes.references/sysmlv2/SysmlAPISchema.json– the element-payload JSON Schema.references/sysmlv2/Systems-Modeling-API.xmi– the underlying service architecture model.references/sysmlv2/SysML-v2-API-Cookbook/– Jupyter notebooks of common query patterns.
sysml-rs extends the spec here. sysml-rs implements the project/commit/model read side of the OMG API as a subset, and extends it with a native REST surface, WebSocket streams, and an MCP transport that share one in-process service – so a model loaded over REST is queryable from an MCP-aware assistant, and vice versa. The route tables, authentication and CORS defaults, session and streaming endpoints, and MCP setup are product documentation and live on the sysml-rs portal: Integrations. If your tooling needs strict OMG conformance for branch operations or the compound query objects, treat sysml-rs as a partial implementation and round-trip through a conformant store for those operations.
Working with a model API
These practices are about the interchange concepts, not any particular implementation; they hold against any conformant store.
Always pin a commit. Reports and exports should record the commit ID that produced them, so results are reproducible and auditable. Without a pinned commit, the same query can return different answers across runs – and nobody can tell whether the model changed or the report did.
Read first, write later. Build trust in your automation by producing useful reports before attempting programmatic model modifications. Read-only automation cannot corrupt model data; teams that jump straight to programmatic writes often create elements that conflict with manual modelling.
Query server-side. Downloading every element and filtering locally works for small models but does not scale. The query surface exists so the store does the filtering.
Keep automation narrow. One well-maintained coverage report is worth more than ten fragile scripts.
A good first target is requirement coverage: query for requirements lacking a verification case, and pair it with a requirement-to-verification traceability matrix (Chapter 10). It is read-only, it produces immediate review value, and it exercises the identity story end to end.
Further reading
- Chapter 15 – how the project/commit interchange notions relate to workspaces and manifests when you are authoring.
- Chapter 10 – the verification semantics behind coverage queries.
- The OMG materials listed above are the normative reference for endpoints, payloads, and the query language.
Appendix D: Tooling Notes (sysml-rs)
The language chapters teach SysML v2 – the OMG specification, KerML, the standard library, and the modelling patterns any conformant tool should support. Real systems work, however, runs on a particular tool, and the particular tool used throughout this book is sysml-rs: an open-source SysML v2 toolchain (parser, analyzer, simulation runtime, language server, REST/MCP service, and editor extensions).
Product documentation for sysml-rs lives on its own portal, where it is maintained against the tool rather than against this book: rickymillar.github.io/sysml-rs. This appendix is the signpost between the two: for each tooling surface the chapters mention, a couple of sentences on what sysml-rs offers and a link to the page that documents it properly. Nothing here is required to write a SysML v2 model.
Spec vs. tooling. Everything in this appendix is sysml-rs-specific. Other SysML v2 tools may behave differently – they may pick different solvers, render diagrams differently, surface different diagnostics, or choose different defaults. Where a topic has a spec-faithful counterpart elsewhere in the book, this appendix cross-references it.
§Getting the tool
There is no published package yet; you build from source. The build has two genuine prerequisites beyond a Rust toolchain: fetching the OMG specification sources (the code generator derives element kinds and validation from them, so a bare cargo build fails without this step) and generating the tree-sitter parser (generated rather than committed; mind the ABI version the README pins, because a parser generated at the wrong ABI compiles and then crashes at parse time). The authoritative, maintained instructions are the Quick start in the sysml-rs README – follow that, not a summary.
§Running a model from the command line
The sysml binary checks files, evaluates expressions, simulates state machines, runs actions, executes verification cases, queries models (stats, find, trace, unverified), and exports PlantUML, canonical JSON, or a declared view’s ViewModel. Worked walkthroughs with real output live in CLI workflows on the portal; sysml help <command> is authoritative for any subcommand.
One caution worth carrying with you: the state-machine simulator can misreport the initial state and transition targets for some models, so cross-check a run against the transitions you wrote before you trust it. This and the other confirmed defects are tracked in Known limitations.
§Solver auto-selection
When the runtime detects the state-space pattern of Chapter 16 §16.B it wires an integrator automatically – a fixed-step Runge-Kutta, an adaptive one, or a stiff-system method – and locates threshold crossings in transition guards with sub-tick precision. You author the model, not the numerics. The runtime’s execution model, and the conscious simplifications it makes (it is a modelling runtime, not a calibrated physics engine), are documented in The runtime; the spec-side story of what the runtime is interpreting is Appendix E.
§Equations workbench
The equations workbench is the interactive surface for the binding-override pattern in Chapter 16 §16.C: it lists the model’s calculations, typesets them as math, exposes bindings as editable fields, and re-evaluates on change – through the same what-if path a sweep script uses, so both see the same answers. See The runtime for the execution substrate it sits on.
§Causal trace and breakpoints
A running session records every value change with its cause, and the trace is queryable backwards from any change (Chapter 16 §16.D). Breakpoints pause a session on model events – entering a state, a transition firing, a constraint flipping to Fail, a threshold crossing, or an arbitrary condition – without touching the model itself. Both are session-level runtime features: The runtime.
Two authoring habits pay off here regardless of tool: name your transitions, constraints, and actions descriptively (a trace through Idle->Heating and BrewTempReq reads far better than one through t_3 and c_17), and keep calculation bodies free of side effects so causal edges stay unambiguous.
§Editor support
The language server provides diagnostics with stable codes (Appendix F), hover with signatures and inherited members, quick-fixes (auto-import, keyword suggestions, ISQ-type replacement), live PASS/FAIL lenses on constraints, and full support for the sysml.toml manifest (Chapter 15). Editor setup and the extension surfaces are documented in Editors.
§Views and diagrams
Two conventions in sysml-rs’s diagrams are spec, not house style: definitions render with sharp corners and usages with rounded corners, as the SysML v2 graphical notation mandates, and every view definition derives from one of the eight standard view definitions, which determine what kind of diagram a view becomes (Chapter 13). Everything else – the card aesthetic, the colour palette, dark-mode behaviour, and the live overlays a running session paints onto a diagram – is sysml-rs rendering, documented in Views and diagrams along with the ViewModel contract diagrams travel over.
§The service, the API, and MCP
One in-process service backs every transport – CLI, language server, REST/WebSocket API, and MCP – so a model loaded through one is visible to the others. The interchange concepts are Appendix C; the route tables, security defaults, and MCP setup are Integrations.
§Worked examples
The models used throughout this book, and a larger catalogue of maintained runnable fixtures (hybrid dynamics, multi-file workspaces, deliberately-broken diagnostic demos), are catalogued in Examples. Every model there is executed by the regression suite, so they work at the commit you checked out.
What’s not in this appendix
The neighbouring appendices carry the topics this one does not:
- Appendix C (The Systems Modeling API) – the OMG interchange API: projects, commits, elements, and why commit-pinning matters.
- Appendix E (Physics-aware Simulation Reference) – the spec-side story of continuous behaviour: the state-space pattern, occurrences, spatial frames, performances.
- Appendix F (Diagnostic Codes) – the diagnostic-code families and how to read live diagnostics.
Appendix E. Physics-aware Simulation Reference
This appendix is the reference for modelling continuous and hybrid behaviour in SysML v2: which language constructs carry physical meaning, what the standard library defines for them, and where the spec ends and a particular runtime begins. The solver machinery that executes these models – integrators, differential-algebraic assembly, numerical mechanics – is product territory and lives in the sysml-rs portal’s runtime documentation; this appendix keeps the language story.
Scope tags. Each section is tagged [SPEC] or [EXTENSION] so you can tell the spec story from the sysml-rs story at a glance. The headline rule: the modelling pattern (specialise
GetDerivative, type ports withISQ::*quantities, write conservation asassert constraint) is spec language. The runtime mechanism that turns the pattern into a numerically integrated trajectory is a sysml-rs extension, and other SysML v2 implementations may execute the same model differently or not at all.
E.1 The state-space representation pattern – [SPEC]
The State-Space Representation (SSR) pattern is how SysML v2 models continuous dynamics. There are three modelling moves; all three are pure spec:
calc def HeaterRhs :> GetDerivative {
in t : Real; in temp : Real;
in heaterPower : Real; in ambientTemp : Real;
attribute heatLossCoeff : Real := 0.2;
attribute thermalMass : Real := 4180.0;
return dT_dt = (heaterPower - heatLossCoeff * (temp - ambientTemp))
/ thermalMass;
}
calc def BrewerReady :> GetOutput {
in temp : Real;
return ready : Boolean = temp >= 90.0 and temp <= 96.0;
}
action def BrewerDynamics :> ContinuousStateSpaceDynamics {
in brewer : Brewer;
action stateEqs : HeaterRhs {
in t = simulationTime;
in temp = brewer.waterTemp;
in heaterPower = brewer.heaterPower;
in ambientTemp = 20.0;
}
}
GetDerivative, GetOutput, and ContinuousStateSpaceDynamics are defined in the standard library’s StateSpaceRepresentation domain package, so that is the package to import when you specialise them. (AnalysisTooling is a different domain package: it defines the ToolExecution and ToolVariable metadata, including the toolName attribute a model can use to request a particular analysis tool.) The author specialises the patterns; a runtime detects the specialisation and wires the integration. A spec-faithful SSR model can be picked up by any conformant runtime.
A note on what’s gone. Earlier sysml-rs material used non-spec metadata fields (
@ToolVariable { derivative = "..." },@ToolExecution { signal = "..." }) to declare ODE structure. Those fields have been removed.@ToolExecution { toolName = ... }remains, becausetoolNameis the spec-defined attribute fromAnalysisTooling; sysml-rs reads it as solver-selection metadata. Old models carrying the removed fields should be rewritten using the SSR pattern above.
E.2 Hybrid models: states with continuous behaviour – [SPEC] pattern, [EXTENSION] execution
A hybrid model is a state machine whose states each select different continuous dynamics – boilers, bouncing balls, valves, and clutches all evolve continuously and jump discretely at thresholds. The modelling recipe is spec language end to end:
- Write a state machine with named states (
Heating,Idle,Falling,Rising). - Write one
calc def :> GetDerivativeper state. - Write transitions whose triggers mention the continuously-varying values (
accept when temp >= 100.0).
Worked snippet (the bouncing ball, condensed from the workspace’s examples/bouncing-ball/):
state def BallMode {
state Falling;
state Rising;
transition first Falling accept when y <= 0.0 and v < 0.0 then Rising;
transition first Rising accept when v <= 0.0 then Falling;
}
calc def FallingRhs :> GetDerivative {
in t : Real; in y : Real; in v : Real;
return dy_dt = v;
return dv_dt = -9.81;
}
calc def RisingRhs :> GetDerivative {
in t : Real; in y : Real; in v : Real;
return dy_dt = v;
return dv_dt = -9.81; // Rising and Falling share gravity; reset of v is in the transition.
}
The transition’s effect (not shown) reverses v with a coefficient of restitution. What executes this – integrating the active state’s derivative, watching every trigger, switching dynamics when a transition fires – is the runtime’s job, not the model’s. How sysml-rs’s runtime does it, and the simplifications it makes, are documented on the runtime page.
E.3 Change triggers and threshold crossings – [SPEC] concept, [EXTENSION] precision
accept when temp >= 100.0 is spec syntax: a change trigger that fires when its condition becomes true. When the condition mentions a continuously-varying value, a runtime has to decide when it became true – and the honest answer usually falls between two integration steps. sysml-rs locates the crossing within the step and fires the transition there, rather than a step late; that is why a thermostat trips cleanly at 100 °C and a bouncing ball bounces at y = 0 instead of slightly below the floor.
Authoring habits that make threshold behaviour clean under any runtime:
- Express thresholds as direct comparisons on a state variable (
temp >= 100.0), not as derived expressions. - Avoid pairs of transitions that would both fire on the same crossing – pick one canonical guard.
- For signals that chatter near a threshold, model an explicit hysteresis margin (
temp >= 100.0to enter,temp <= 95.0to leave).
E.4 Occurrences and Life – [SPEC]
The spec’s Occurrence model (Occurrences.kerml in the Kernel Semantic Library) gives every happening a temporal identity. A Life is the complete temporal extent of an occurrence; an occurrence’s snapshots are its instantaneous time slices; and the library defines temporal relations between occurrences – HappensBefore (one occurrence completes before another begins) and HappensDuring (one occurrence’s extent lies within another’s).
This is the lifecycle layer everything dynamic hangs off. A simulation trajectory is an occurrence unfolding; a verdict stream is a time-indexed reading of snapshots; “did the pump start before the valve opened?” is a HappensBefore question. Chapter 16 §16.A develops the run-as-occurrence framing; Appendix J covers the occurrence vocabulary in depth.
E.5 Spatial frames – [SPEC]
SpatialFrames.kerml (Kernel Semantic Library) supplies the spec’s spatial surface. A SpatialFrame is a three-dimensional body that provides a spatial extent for positions to be measured against, and the library defines functions such as PositionOf (the position of a point relative to a frame at a given time) and CurrentPositionOf (the same, against a clock’s current time).
A model that imports SpatialFrames::* can declare frames, place occurrences within them, and ask “where is X, in frame F, at time t?” as a model-level query. Spatial frames matter most for multi-body mechanical systems and any model that must switch between body-fixed and inertial coordinates.
E.6 Performances and Evaluation – [SPEC]
Performances.kerml (Kernel Semantic Library) defines a Performance as an execution of a behaviour – itself a kind of occurrence, so everything in E.4 applies to it. An Evaluation is the performance of a function: the act of computing a result, as an occurrence with its own temporal extent, with BooleanEvaluation as the predicate case. StatePerformances.kerml adds StatePerformance and StateTransitionPerformance – a state being occupied, a transition being taken – plus helpers such as allSubstatePerformances for walking a nested state machine’s performance record.
This is the layer verification ultimately reads: when a verification case runs against a simulation (Chapter 10), the constraint checks it performs over the trajectory are, in the library’s terms, boolean evaluations over performances.
E.7 What sysml-rs’s runtime adds – [EXTENSION]
sysml-rs extends the spec here. When a model types its ports and attributes with
ISQ::*quantities (Chapter 14), sysml-rs classifies each port’s physical role – effort, flow, displacement, momentum, and friends – from the type’s dimensions, and synthesizes the corresponding conservation obligations at connection junctions. Nobody writes “current in equals current out”; the runtime adds that obligation when it sees the topology, and a violated balance surfaces as an ordinary Fail verdict. On top of that, the runtime selects a numerical integrator automatically (fixed-step and adaptive Runge-Kutta methods, and a stiff-system method), honouring@ToolExecution { toolName = ... }when a model requests one explicitly.None of this is spec, and sysml-rs’s physics is deliberately simplified – it executes idealised models for insight, and is labelled as such; it is not a calibrated physics engine. The mechanism, its limits, and the confirmed defects are documented on the portal: The runtime and Known limitations.
Tooling cross-references. The physics diagnostic family (PH codes, including the “did you mean ISQ type?” quick-fix) is described in Appendix F. Runnable physics examples – bouncing-ball, damped-oscillator, dc-motor, the espresso fixtures, and the rest – are catalogued on the portal’s Examples page.
Appendix F. Diagnostic Codes
The chapters cite diagnostic codes inline – E200 for an unresolved name, S001 for a duplicate member, PH006 for a Real that could be an ISQ type. This appendix explains where those codes come from, what the families mean, and how to look a code up. What it deliberately does not contain is an exhaustive hand-maintained code table: earlier editions carried one, it drifted from the tool (codes appeared, wordings changed, at least one severity was listed wrongly), and a reference that silently rots is worse than none. The live tool output is authoritative; a reference generated from the tool’s own source is planned for the sysml-rs portal.
Tag:
[TOOLING]. The codes are sysml-rs’s diagnostic vocabulary – other SysML v2 implementations will emit a different set or use different identifiers. The checks behind the codes (e.g. “every requirement should have a verification case”) are spec-faithful where applicable; the labels are sysml-rs.
Where diagnostics come from
sysml-rs emits diagnostics from two layers:
- The core analyzer checks structure and semantics as it elaborates a model: ownership integrity, name resolution, well-formedness of memberships and specializations, property validation, import health, and physics typing.
- The runtime’s health checks look at what a model would do when executed: state machines with unreachable states, actions with no steps, flows with mismatched endpoints, verification cases that would vacuously pass, constraints that cannot be evaluated.
Every diagnostic carries a severity – error (the model does not parse, validate, or execute), warning (structurally valid but semantically suspect), or info (a non-blocking observation, often intentional) – and info-level codes deserve a review, not a reflexive fix.
The code families
Codes are grouped by prefix into families. These are the families the chapters reference; the set is open-ended and grows with the tool:
| Prefix | Family | Cross-reference |
|---|---|---|
E | Structural integrity and name resolution (E200 is the unresolved-name code behind the auto-import quick-fix) | Chapter 4 |
S | Semantic well-formedness: duplicate names, misplaced memberships, illegal typings | Chapter 3, Chapter 12 |
V | Property validation | – |
IM | Imports: circular chains, duplicates, unused imports | Chapter 4 |
SM | State machines: missing initial states, unreachable states, dead ends | Chapter 8 |
AX | Actions: empty bodies, unreachable steps, malformed control nodes | Chapter 7 |
FL | Flows and ports: missing endpoints, type mismatches, direction conflicts | Chapter 6 |
VC | Verification cases: missing subjects, vacuous verification | Chapter 10 |
CN | Constraints: uncompilable or non-boolean expressions, violations | Chapter 9 |
RQ | Requirements: unsatisfied, unverified, unresolvable references | Chapter 10 |
PH | Physics: incompatible domains, conservation imbalance, ISQ-type suggestions (PH006 carries the “did you mean ISQ type?” quick-fix) | Chapter 14, Appendix E |
Looking a code up
When a diagnostic fires, read it where it lives:
- On the command line,
sysml inspect --diagnostics <file>reports every diagnostic for a file or workspace, with its code, severity, and span. The surrounding CLI workflow – checking, querying, and inspecting models – is documented on the sysml-rs portal: CLI workflows. - In the editor, the same diagnostics appear as squiggles with the code attached, and codes with quick-fixes (auto-import for
E200, the ISQ replacement forPH006, and others) offer them in place. See Editors for setup.
The message text is written to stand alone – it names the element, says what is wrong, and usually says what to do – so the code is a stable handle for searching and suppressing, not something you should need a table to decode.
When the tool is wrong
A diagnostic can itself be the defect: some codes are known to fire on well-formed models. Confirmed cases – with reproductions and workarounds where they exist – are tracked on the portal’s Known limitations page. If a diagnostic contradicts what you can verify against the specification, check that page before rewriting a correct model.
Appendix G: Relationship & Element Kinds Reference
Appendix A answers “how do I write this?”. This appendix answers a different question: “what is this, and does it actually exist?”
Every construct you write in SysML v2 produces one or more elements in an underlying metamodel – a fixed set of classes with fixed names and fixed inheritance. When you write part engine : Engine; you create a PartUsage whose typing is a FeatureTyping; when you write :> you create a Subclassification or a Subsetting depending on context. Tool APIs, model interchange files, diagnostic messages, and the specification itself all speak in these class names, so sooner or later you will meet one and need to know what it is.
The tables below are that dictionary. Read them in this direction:
- Metamodel class is the authoritative name, as declared in the OMG vocabulary files. Where the class lives in KerML rather than SysML, that is noted – SysML inherits the whole KerML metamodel, so a great many “SysML” relationships are really KerML’s.
- Specializes is the direct parent from
rdfs:subClassOf. This is the fastest way to understand an unfamiliar kind:RedefinitionspecializesSubsettingspecializesSpecialization, which tells you almost everything about how redefinition behaves. - Textual form is what you type. Some kinds have a keyword, some have a punctuation token, some have both, and a few have no textual notation at all – they exist only as structure the parser creates for you. That last group is called out explicitly, because knowing you cannot write something directly saves you looking for a syntax that does not exist.
- Clause is where the specification defines the notation. Clause numbers prefixed
KerMLare from the KerML specification; the rest are SysML.
Nothing here is extrapolated. Every row was checked against the vocabulary files, the specification text, and the reference grammar, and the handful of names that turn out not to exist are listed in their own section rather than quietly dropped.
A keyword does not imply a metaclass
Before the tables, one principle, because it is the single most common way to get lost in a reference like this: the notation and the metamodel are two separate vocabularies, and they do not line up one-to-one. A keyword you can type is not a promise that a class of the same name exists, and a class that exists is not a promise that there is a keyword for it. All three combinations occur:
| Has a keyword | No keyword | |
|---|---|---|
| Has a metaclass | ReturnParameterMembership, written return | ResultExpressionMembership, written as a bare trailing expression |
| No metaclass | message, which produces a FlowUsage | nothing to name, so nothing to look up |
So when a search of the vocabulary for some name comes up empty, that is not yet an answer. It rules out one of two possibilities, and the keyword may still be perfectly normative notation for a class with a different name. Each of the three cells above is worked through below – message under Messages, the two memberships in the membership table – and the same reasoning is what separates the genuinely non-existent names in Names that do not exist from the merely differently-named ones.
The specialization family
These are the relationships you write as a token inside a declaration. They all descend from Specialization, and each has both a symbol and an equivalent keyword. The pairs are fixed by the specification’s lexical structure, not by convention.
| Metamodel class | Specializes | Textual form | Clause |
|---|---|---|---|
Specialization | Relationship | :> or specializes | KerML 7.3.2.3 |
Subclassification | Specialization | :> or specializes (on a definition) | KerML 7.3.3.3 |
FeatureTyping | Specialization | : or defined by | KerML 7.3.4.3 |
Subsetting | Specialization | :> or subsets | KerML 7.3.4.4 |
Redefinition | Subsetting | :>> or redefines | KerML 7.3.4.5 |
ReferenceSubsetting | Subsetting | ::> or references | 7.13.2 |
CrossSubsetting | Subsetting | => or crosses | 7.13.2, 8.4.9.1 |
The six symbol/keyword pairs are defined as lexical terminals in clause 8.2.2.1.2, which is worth reading once because it is the only place they are all listed together:
DEFINED_BY = ':' | 'defined' 'by'
SPECIALIZES = ':>' | 'specializes'
SUBSETS = ':>' | 'subsets'
REFERENCES = '::>' | 'references'
CROSSES = '=>' | 'crosses'
REDEFINES = ':>>' | 'redefines'
Two of these deserve a note. First, :> is genuinely ambiguous in the source – it creates a Subclassification on a definition and a Subsetting on a feature – so the metamodel class you get depends on what you attached it to, not on the token. Second, in SysML the long form of : is defined by, not typed by; typed by is the KerML spelling of the same terminal and typed is not a SysML keyword at all.
CrossSubsetting is the newest member of the family and is not covered elsewhere in this book. It lets a binary connection definition declare that each end must be one of the values of a feature reached through the other end, which keeps the two sides of the connection coordinated:
package CrossExample {
part def Device {
part connectingHub : Hub[1];
}
part def Hub {
part connectedDevice : Device[*];
}
connection def DeviceToHub {
end part device : Device crosses hub.connectedDevice;
end part hub : Hub crosses device.connectingHub;
}
}
Relationships with their own declaration syntax
These are written as standalone statements rather than as a token on some other declaration.
| Metamodel class | Specializes | Textual form | Clause |
|---|---|---|---|
Dependency | Relationship | dependency [<name> from] <clients> to <suppliers>; | 7.3.2 |
Annotation | Relationship | none directly; created by doc, comment, and bare /* */ (// and //* notes are discarded at parse time and create no element) | 7.4.1 |
Import | Relationship | import | 7.5.3 |
MembershipImport | Import | import <ns>::<member>; | 7.5.3 |
NamespaceImport | Import | import <ns>::*; | 7.5.3 |
Expose | Import | expose (inside a view) | 7.26.2 |
MembershipExpose | Expose, MembershipImport | expose <ns>::<member>; | 7.26.2 |
NamespaceExpose | Expose, NamespaceImport | expose <ns>::*; | 7.26.2 |
Conjugation | Relationship | ~ (see note) | KerML 7.3.2.4 |
PortConjugation | Conjugation | created implicitly with every port def | 7.12.3 |
ConjugatedPortTyping | FeatureTyping | port <name> : ~<PortDef>; | 7.12.3 |
Disjoining | Relationship | disjoint from (KerML only) | KerML 7.3.2.5 |
Unioning | Relationship | unions (KerML only) | KerML 7.3.2.7 |
Intersecting | Relationship | intersects (KerML only) | KerML 7.3.2.7 |
Differencing | Relationship | differences (KerML only) | KerML 7.3.2.7 |
FeatureChaining | Relationship | . in SysML; chains in KerML | 7.6.6, KerML 7.3.4.6 |
FeatureInverting | Relationship | inverse of (KerML only) | KerML 7.3.4.7 |
TypeFeaturing | Relationship | featured by (KerML only) | KerML 7.3.4.8 |
Conjugation is a general KerML relationship that flips the direction of every feature of a type, and KerML writes it as ~ or conjugates on any type declaration. SysML exposes only the port-specific slice of it: every port def silently acquires a ConjugatedPortDefinition via a PortConjugation, and you reach it by writing ~ in front of the port definition’s name when typing a port. There is no general conjugates keyword in SysML, and conjugates is not in the SysML keyword list.
The last four rows – chains, inverse of, featured by, and the set operators – are the clearest example of a real kind with no SysML notation. All four classes are in the SysML vocabulary, because SysML inherits KerML’s metamodel wholesale, but none of the keywords appears in the SysML reserved-keyword list in clause 8.2.2.1.2, and the SysML specification text uses phrases like “featured by” only as prose describing semantics. In SysML you get FeatureChaining by writing a dotted path, and you do not get FeatureInverting or TypeFeaturing at all.
Tool note.
sysml-rsuses one unified grammar for.sysmland.kermlfiles, so it will happily acceptchains,featured by, andinverse ofin a.sysmlfile and build real syntax nodes for them. Other tools are entitled to reject them. Do not rely on this.
Membership kinds
A membership is the relationship between a namespace and something inside it. Almost all of them are created structurally – you write a nested declaration and the parser decides which membership class it produced – but a surprising number do have a keyword, and those are the ones worth memorising.
| Metamodel class | Specializes | Textual form | Clause |
|---|---|---|---|
Membership | Relationship | none; created by nesting. Visibility via public / private / protected | KerML 7.2.5.2 |
OwningMembership | Membership | none; created by nesting | KerML 7.2.5.2 |
FeatureMembership | OwningMembership | none; created by nesting a feature in a body | KerML 7.3.2.6 |
EndFeatureMembership | FeatureMembership | end | 7.13.2 |
ParameterMembership | FeatureMembership | in / out / inout | 7.6.3 |
ReturnParameterMembership | ParameterMembership | return | 7.19.2 |
ResultExpressionMembership | FeatureMembership | none; the trailing bare expression of a body | 7.19.2 |
ElementFilterMembership | OwningMembership | filter <booleanExpr>; | 7.5.4 |
SubjectMembership | ParameterMembership | subject | 7.22.2 |
ActorMembership | ParameterMembership | actor | 7.21.2 |
StakeholderMembership | ParameterMembership | stakeholder | 7.21.2 |
ObjectiveMembership | FeatureMembership | objective | 7.22.2 |
RequirementConstraintMembership | FeatureMembership | assume or require | 7.21.2 |
FramedConcernMembership | RequirementConstraintMembership | frame | 7.21.2 |
RequirementVerificationMembership | RequirementConstraintMembership | verify | 7.24.2 |
StateSubactionMembership | FeatureMembership | entry / do / exit | 7.18.2 |
TransitionFeatureMembership | FeatureMembership | accept (trigger) / if (guard) / do (effect) | 7.18.3 |
VariantMembership | OwningMembership | variant; also each enum literal | 7.6.7 |
ViewRenderingMembership | FeatureMembership | render | 7.26.2 |
ReturnParameterMembership and ResultExpressionMembership are the two most commonly doubted names on this list, and both are real KerML metaclasses, each with its own abstract-syntax subclause – KerML 8.3.4.7.8 and 8.3.4.7.7 respectively. They are the mirror image of message: where message is a keyword with no class behind it, these are classes, and only one of them has a keyword. The normative notation in KerML 8.2.5.7.1 shows the difference in two lines:
ReturnFeatureMember : ReturnParameterMembership = MemberPrefix 'return' ownedRelatedElement += FeatureElement
ResultExpressionMember : ResultExpressionMembership = MemberPrefix ownedRelatedElement += OwnedExpression
One has 'return' between the prefix and the element; the other has nothing at all. A calc def body can carry both at once – a named, typed return parameter, and a trailing expression that supplies its value:
package ResultExample {
calc def RectangleArea {
in width : ScalarValues::Real;
in height : ScalarValues::Real;
return area : ScalarValues::Real;
width * height
}
}
The return area : ScalarValues::Real; line is the ReturnParameterMembership. The bare width * height on the last line is the ResultExpressionMembership: there is no keyword to write, and the only thing that marks it is its position as the final expression in the body. If you go looking for a result keyword, you will not find one.
Relationship-shaped usages
SysML’s connectors, flows and behavioural references are relationships and usages at the same time. That double life is visible in the Specializes column, where you will often see two parents.
| Metamodel class | Specializes | Textual form | Clause |
|---|---|---|---|
Connector | Feature, Relationship | connect <a> to <b> | 7.13.2 |
BindingConnector | Connector | bind <a> = <b>; | 7.13.3 |
BindingConnectorAsUsage | BindingConnector, ConnectorAsUsage | binding <name> bind <a> = <b>; | 7.13.3 |
Succession | Connector | first <a> then <b>; / then <b>; | 7.13.5 |
SuccessionAsUsage | ConnectorAsUsage, Succession | succession <name> first <a> then <b>; | 7.13.5 |
Flow | Connector, Step | flow <src> to <tgt>; | 7.16.2 |
FlowUsage | ActionUsage, ConnectorAsUsage, Flow | flow or message (see below) | 7.16.2 |
SuccessionFlow | Flow, Succession | succession flow <src> to <tgt>; | 7.16.2 |
SuccessionFlowUsage | FlowUsage, SuccessionFlow | succession flow | 7.16.2 |
AllocationUsage | ConnectionUsage | allocate <a> to <b>; | 7.15.2 |
TransitionUsage | ActionUsage | transition [<name>] first <src> ... then <tgt>; | 7.18.3 |
AssignmentActionUsage | ActionUsage | assign <target> := <expr>; | 7.17.9 |
PerformActionUsage | ActionUsage, EventOccurrenceUsage | perform action <name> or perform <ref> | 7.17.6 |
ExhibitStateUsage | PerformActionUsage, StateUsage | exhibit state <name> or exhibit <ref> | 7.18.4 |
IncludeUseCaseUsage | PerformActionUsage, UseCaseUsage | include use case <name> or include <ref> | 7.25.3 |
EventOccurrenceUsage | OccurrenceUsage | event occurrence <name> or event <ref> | 7.9.5 |
AssertConstraintUsage | ConstraintUsage, Invariant | assert constraint { ... } or assert <ref> | 7.20.3 |
SatisfyRequirementUsage | AssertConstraintUsage, RequirementUsage | satisfy requirement <ref> by <part>; | 7.21.4 |
Notice the pattern in the last several rows: each has a long form that declares something new, and a short form that just names an existing element. That short form is not a special case per construct – it is always the same mechanism. perform a;, exhibit s;, include u;, assume c;, require c;, verify r;, assert c;, event e;, and a bare entry a; all attach the named element by a ReferenceSubsetting, which is why ::> and references appear so far from the connection chapter that introduces them. The specification says so explicitly in clauses 7.9.5, 7.17.6, 7.18.2, 7.18.4, 7.20.3, 7.21.2 and 7.24.2.
Messages
message is the clean worked example of the principle above, and it is worth spelling out because two apparently contradictory statements about it are both true:
messageis real, normative SysML v2 syntax. It is in the reserved-keyword list in clause 8.2.2.1.2, it has a normative grammar production in clause 8.2.2.16, and the specification uses it in its own examples.- There is no
Messageclass in the metamodel. SearchingSysML-vocab.ttlandKerml-Vocab.ttlfor one returns nothing, and that is the correct result, not a gap in the search.
The reconciliation is in the production’s return type. Clause 8.2.2.16 (Flows Textual Notation) declares:
Message : FlowUsage =
OccurrenceUsagePrefix 'message'
MessageDeclaration DefinitionBody
{ isAbstract = true }
Message : FlowUsage – the name on the left is the grammar rule, and the class it constructs is FlowUsage. So message is concrete syntax for an existing metaclass, not the name of a new one. Anyone who says the keyword is real is right; anyone who says the metaclass does not exist is also right; and Message is a production name that never becomes a class.
Two further things exist under the same name, which is what makes it such reliable bait:
Flows::Message, anabstract flow defin the shipped Systems library, described there as “the base type of all FlowUsages”.Flows::Flowspecializes it directly, andFlows::SuccessionFlowspecializesFlows::Flowin turn. This is a library type, not a metaclass.Transfers::MessageTransfer, a KerML libraryinteraction, whichFlowTransferis declareddisjoint from. Also a library element.
So the way to think about message is as one of the three kind keywords for a flow usage, alongside flow and succession flow (clause 7.16.2). A message differs from a streaming flow in what it identifies at its ends: a flow names an out feature of the source and an in feature of the target, whereas a message names the source and target events between which a transfer may happen. The { isAbstract = true } in the production above is why a message is always abstract whether or not you write abstract.
package MessageExample {
attribute def ControlSignal;
part def Vehicle {
part controller {
event occurrence sendControl;
}
part engine {
event occurrence receiveControl;
}
message throttleCmd of ControlSignal
from controller.sendControl
to engine.receiveControl;
}
}
The declaration parts are all optional, and this is the general shape:
message [<name>] [of [<payloadName> :] <PayloadType>] [from <sourceEvent> to <targetEvent>];
message <sourceEvent> to <targetEvent>;
The name, the of payload clause, and the from/to event pair are each independently omissible. The payload clause can also carry a multiplicity, and the : before the payload type is dropped along with the payload name when you omit it, which is why of ControlSignal above needs no colon. The second line is a separate grammar alternative: with no name and no payload you may drop from and write the two events with only to between them. If you omit the payload specification entirely, the message places no constraint on what may be transferred; and if you omit the source and target events instead, you may supply a feature value for the message in their place (clause 7.16.2).
Here is how the forms fare in practice. Every one of them produces a message_usage node in the tree, so the keyword is genuinely recognised in all four cases:
| Form | Grammar alternative | sysml-rs |
|---|---|---|
message <name> from <src> to <tgt>; | first | accepted, no diagnostics |
message <src> to <tgt>; | second | accepted, no diagnostics |
message of <Type> from <src> to <tgt>; | first | accepted; one info on payload typing |
message; | first, everything omitted | parses, but rejected with “missing source endpoint” / “missing target endpoint” |
The first two are the forms worth remembering, and both are exactly what the disputed claim said they were.
Tool note. The last row is the one divergence. A bare
message;is grammatically legal – every part of the first alternative is optional – andsysml-rsdoes build amessage_usagenode for it, but then raises two errors demanding endpoints. It is a degenerate declaration with nothing to say, so this costs you nothing in practice; it is listed only so the errors are not mistaken for a problem with your model.
For contrast, the streaming-flow sibling. Note what changes: a flow def declares its ends and can constrain the payload by redefining it, and the usage names an out feature on one side and an in feature on the other, rather than a pair of events.
package FlowPair {
item def Fuel;
part def FuelTank { out fuelOut : Fuel; }
part def Engine { in fuelIn : Fuel; }
flow def FuelFlow {
ref item :>> payload : Fuel;
end tank : FuelTank;
end eng : Engine;
}
part def Vehicle {
part tank : FuelTank;
part eng : Engine;
flow supply : FuelFlow from tank.fuelOut to eng.fuelIn;
}
}
FlowDefinition, FlowUsage and SuccessionFlowUsage are all real metaclasses, unlike Message – which is why the flow rows in the table above carry class names and the message row does not.
Names that do not exist
Each of the following is a plausible-sounding name that is not a metamodel class. They are listed rather than omitted because they circulate in tutorials, in tool output, and in earlier drafts of this book, and a confirmed negative is more useful than silence. Every one was checked against Kerml-Vocab.ttl, SysML-vocab.ttl, KerML-shapes.ttl, SysML-shapes.ttl, both specification texts, and the reference grammar.
| Name you may see | Verdict | What actually exists |
|---|---|---|
Message | Not a metamodel class – but a real grammar rule name | The keyword message, normative in clause 8.2.2.16, producing a FlowUsage; plus Flows::Message in the standard library |
Trace | Does not exist anywhere in the language | Nothing. trace appears only as jazz_am:trace, an OSLC interchange property in the API shapes files – not a SysML relationship and not a keyword |
Assignment | Not a metamodel class | AssignmentActionUsage |
Allocation | Not a metamodel class | AllocationDefinition and AllocationUsage |
Transition | Not a metamodel class | TransitionUsage |
Perform | Not a metamodel class | PerformActionUsage |
Exhibit | Not a metamodel class | ExhibitStateUsage |
Include | Not a metamodel class | IncludeUseCaseUsage |
Frame | Not a metamodel class | FramedConcernMembership |
RequireConstraintUsage | Not a metamodel class | RequirementConstraintMembership, whose kind is require or assume |
AssumeConstraintUsage | Not a metamodel class | as above |
Composition | Not a metamodel class | Feature::isComposite, a Boolean property. A usage is composite unless you write ref (clause 7.6.3) |
Portion | Not a metamodel class | PortionKind, an enumeration on OccurrenceUsage, written snapshot or timeslice (clause 7.9.3) |
OwningFeatureMembership | Not a metamodel class | FeatureMembership, which already specializes OwningMembership |
MessageTransfer | Not a metamodel class | Transfers::MessageTransfer, a KerML library interaction. FlowTransfer is declared disjoint from it |
ReturnParameterMembership and ResultExpressionMembership have also been challenged, and they are the opposite case to Message: both do exist as metaclasses, each with its own dedicated abstract-syntax subclause in the KerML specification (ResultExpressionMembership at KerML 8.3.4.7.7, ReturnParameterMembership at KerML 8.3.4.7.8). See the membership table above, and the worked example below it.
Definition and usage pairs
Most SysML element kinds come as a matched pair: a def that declares a reusable type, and a usage that places one in a context. The definition keyword is always the usage keyword plus def.
| Definition | Usage keyword | Metamodel class | Covered? |
|---|---|---|---|
attribute def | attribute | AttributeDefinition | ch05 |
enum def | enum | EnumerationDefinition (specializes AttributeDefinition) | ch05 |
item def | item | ItemDefinition | ch05 |
part def | part | PartDefinition | ch05 |
port def | port | PortDefinition | ch05 |
occurrence def | occurrence | OccurrenceDefinition | reference only |
connection def | connection | ConnectionDefinition | ch06 |
interface def | interface | InterfaceDefinition | ch06 |
allocation def | allocation | AllocationDefinition | ch06 |
flow def | flow | FlowDefinition | ch06 |
action def | action | ActionDefinition | ch07 |
state def | state | StateDefinition | ch08 |
calc def | calc | CalculationDefinition | ch09 |
constraint def | constraint | ConstraintDefinition | ch09 |
requirement def | requirement | RequirementDefinition | ch10 |
concern def | concern | ConcernDefinition | ch10 |
case def | case | CaseDefinition | ch10 |
analysis def | analysis | AnalysisCaseDefinition | ch10 |
verification def | verification | VerificationCaseDefinition | ch10 |
use case def | use case | UseCaseDefinition | ch10 |
view def | view | ViewDefinition | ch13 |
viewpoint def | viewpoint | ViewpointDefinition | ch13 |
rendering def | rendering | RenderingDefinition | ch13 |
metadata def | metadata, or the symbol @ | MetadataDefinition | ch13 |
That is the complete set. The vocabulary declares exactly one more *Definition class, ConjugatedPortDefinition, and it has no keyword of its own because you never declare one – every port def generates its conjugate automatically, and you reach it as ~<PortDef>.
Two things that look like they belong in this table but do not:
- There is no
package def.PackagespecializesNamespace, notDefinition, so there is no definition/usage split to have. You writepackage <Name> { ... }and that is the whole story. connectis not the connection usage keyword. The usage keyword isconnection;connectintroduces the ends. Both of these are valid, and the second is the short form you get when the declaration part is empty:
connection <name> : <ConnectionDef> connect <a> to <b>;
connect <a> to <b>;
The same shape applies to allocations (allocation <name> : <Def> allocate <a> to <b>;, or just allocate <a> to <b>;) and to bindings (binding <name> bind <a> = <b>;, or just bind <a> = <b>;).
Metadata is worth one extra note, because it has two notations that are easy to confuse and only one of them is a @:
@<MetadataDef> [{ <body> }] // a metadata usage, as a member
#<MetadataDef> <declaration> // a user-defined keyword, as a prefix
The @ form is a metadata usage attached to the element that owns it, and @ is simply the symbolic equivalent of metadata ... defined by ... (clause 7.27.2). The # form is different in kind: it is a user-defined keyword, placed immediately before the reserved keyword of a declaration, and if the named metadata definition specializes SemanticMetadata it also implies a specialization (clause 7.27.4). So #situation occurrence def Failure; declares an OccurrenceDefinition that implicitly subclassifies whatever situation’s base type names. With semantic metadata you may even drop the language keyword entirely and write #situation def Failure;.
Verifying the examples
Every sysml block in this appendix parses with no errors and no error nodes in the tree. The forms were checked with:
sysml --quiet inspect --no-stdlib --diagnostics --json <file>
sysml --quiet inspect --no-stdlib --cst <file>
The second command matters as much as the first. A construct that is not really in the grammar can still parse without complaint, by being absorbed as an ordinary identifier – so the check is not “did it parse?” but “did a node for this construct appear in the tree?”. For the three examples above, the tree contains message_usage, crosses_clause, return_feature and result_expression nodes, which is what makes them evidence rather than guesswork.
The message example does draw one info-level advisory, that its payload type is not matched by a feature type on its endpoints. That is expected: a message names events at its ends rather than typed ports, so there is nothing there to carry the payload type. The example is structurally the same as the specification’s own in clause 7.16.2.
Tool note. Three spec-legal forms are not yet accepted by
sysml-rs, so if you meet them in someone else’s model do not assume the model is wrong.
frame <ref>;inside a requirement body is rejected with “Unexpected keywordframe”, though the long formframe concern <name> : <Concern>;works. Clause 7.21.2 permits both.verify <ref>;is accepted inside anobjective { ... }block but not directly in a verification definition body, whereverify requirement <ref>;is required instead. Clause 7.24.2 permits both.#<MetadataDef>as a declaration prefix produces “Syntax error near#...”. The#token is recognised, but it cannot yet be attached to the declaration that follows it. The@metadata usage form is unaffected and parses cleanly.
Source anchors
- Metamodel vocabulary:
references/sysmlv2/SysML-vocab.ttl,references/sysmlv2/Kerml-Vocab.ttl–rdfs:subClassOfgives theSpecializescolumn,rdfs:commentthe descriptions - API interchange shapes:
references/sysmlv2/SysML-shapes.ttl,references/sysmlv2/KerML-shapes.ttl– note these carry OSLC properties such asjazz_am:tracethat are not language constructs - Specification text:
references/sysmlv2/derived/SysML-spec-r2025-04.txt,references/sysmlv2/derived/KerML-spec-r2025-04.txt– source of every clause number above - Reference grammar:
references/sysmlv2/SysML-v2-Pilot-Implementation/org.omg.sysml.xtext/src/org/omg/sysml/xtext/SysML.xtext, plus…/org.omg.kerml.xtext/…/KerML.xtextand…/KerMLExpressions.xtext - Shipped standard library, for the spec authors’ own usage:
libraries/standard/library.kernel/andlibraries/standard/library.systems/–Flows.sysmlandTransfers.kermlare whereMessageandMessageTransferlive
Appendix H: Lexical Reference
The chapters teach constructs. This appendix covers the smaller rules underneath them – how names, literals, and comments are actually written – and in particular the two forms the chapters use without stopping to explain: root-qualified names and quoted names.
Names
An ordinary name is an identifier: CoffeeMachine, grindSize, waterTank.
The book’s convention, and a common one, is UpperCamelCase for definitions and
lowerCamelCase for usages and attributes. Nothing in the language enforces
that, but a reader scanning a model relies on it.
Qualified and root-qualified names
<Package>::<Member>
$::<Package>::<Member>
part grinder : CoffeeMachineDomain::Definitions::Grinder;
A qualified name walks down from a visible namespace. Reach for one when a bare name would be ambiguous across packages.
Prefixing $:: makes it a root-qualified name: lookup starts at the model
root rather than the enclosing scope. It is the escape hatch for the case where
a local name shadows the one you meant.
Quoted names, for names that are not identifiers
<'<short-name>'> '<long name>'
requirement def <'REQ-TEMP-01'> 'Extraction Temperature' {
doc /* Water temperature shall remain within operating range. */
}
Read it as: this requirement’s short name is REQ-TEMP-01 and its name is
Extraction Temperature.
A name in single quotes is an unrestricted name, and it may contain
characters an identifier cannot – spaces, hyphens, punctuation. Angle brackets
declare a short name, which is why requirement identifiers like REQ-TEMP-01
are written <'REQ-TEMP-01'>: the hyphens would otherwise be read as
subtraction. To put a quote inside such a name, escape it: 'REQ\'TEMP-02'.
This is the form Chapter 10 and Chapter 11 use for requirement identifiers.
Use quoted names where they earn their keep – requirements and cases that carry an external identifier or a human-readable title – and plain identifiers everywhere else.
Literals
attribute grindSize : Real = 0.4;
attribute enabled : Boolean = true;
attribute label : String = "Main Grinder";
- Booleans are
trueandfalse. - Strings are double-quoted.
- Reals may lead with a dot –
.5is valid – and may use scientific notation:1e3,9.8e-2. nullis the null literal.()is an equivalent spelling of the same empty value, which is worth recognising when you meet it in library sources.*is the infinity literal. It is what makes[0..*]mean “any number of” in a multiplicity, and it is the same token as multiplication – position tells them apart.
Comments, and why doc is different
part def Brewer {
doc /* Heats and pressurizes water for extraction. */
//* A note to whoever is editing this file next.
attribute waterTemp : Real = 93.0;
}
doc creates a documentation element in the model. It survives into the
model itself, so tools can show it on hover, carry it into a generated document,
and export it through the API.
//* notes do not. Per the specification’s own grammar they are hidden
tokens, discarded during parsing in the same way whitespace is. They never
become part of the model.
So the choice is not stylistic. If the sentence explains what an element is,
it belongs in doc, where a reviewer who never opens the source file will still
see it. If it is a message to the next person editing the file, //* is right.
The most common mistake here is writing model meaning into a note and then wondering why it does not appear anywhere in the tooling.
Marking the language of documentation
part def Brewer {
doc locale "en-GB" /* Heats and pressurises water for extraction. */
}
A doc may carry a locale – a string naming the natural language the
documentation is written in. It is optional, and worth setting on a model whose
documentation is translated or whose readership is mixed, because it lets a tool
pick the right text rather than guessing. Comments take the same modifier.
Embedding another language
The specification also provides a way to carry a fragment of some other language inside a model element:
[rep <name>] language "<language-name>" /* <the fragment> */
This is a textual representation (SysML §8.2.2.4.3). The intent is that a constraint expressed in OCL, a formula in MathML, or a snippet of source can travel with the element it belongs to, tagged with what language it is in, so a tool that understands that language can pick it up and one that does not can leave it alone.
Not available in sysml-rs. The parser does not accept this form. Worse, a
repline without the string is absorbed as a pair of ordinary names rather than reported as an error, so it fails quietly. The construct is in the specification and in the grammar; it is simply not implemented here, and this book does not use it.
Appendix I: Modeling Review Checklists
Model review is a team activity, not a solo exercise. You bring a model to a review gate, a group of peers asks hard questions about completeness and correctness, and the model either passes or goes back for revision. The problem is that “completeness” is vague. Without a written standard, reviewers focus on whatever they happen to notice, and different reviewers notice different things.
This appendix gives you four copy-ready checklists, one for each major review type: Architecture, Requirements, Behavior, and Verification. Each checklist is a set of yes/no questions phrased so that “yes” means the model is correct in that dimension. A “no” or “unsure” answer identifies a specific gap that needs attention before the review can pass.
How to Use These Checklists
Copy the relevant checklist into your review document, pull request description, or team wiki page before the review meeting. Work through each item independently: read the question, inspect the model, and mark it. Do not answer from memory – open the .sysml files and check.
The checklist items are sequenced so that earlier items catch structural problems that would make later items meaningless. If item 1 fails, fix it before assessing item 3.
Each item includes a chapter reference in parentheses. If a question is unclear, that chapter explains the underlying construct in full.
Not every item is a language rule. Items marked Convention: or Style: describe review heuristics – a model is perfectly legal either way. They earn their place because they catch review pain later, but a team can consciously adapt or waive them.
Note: These checklists target model completeness and structural consistency. They are not a substitute for domain correctness review. A model can pass every checklist item and still describe a system that does not work in the real world. Use domain experts alongside these checklists.
The Coffee Machine example developed throughout this book is a useful validation target. After reading this appendix, you can apply each checklist to the files in examples/coffee-machine/ and observe which items pass immediately and which require model elaboration.
Checklist 1: Architecture Review
Use this checklist during a structural architecture review, typically at the end of the definition phase, before behavioral modeling begins. It verifies that the structural model has enough completeness and internal consistency to serve as a scaffold for behaviors, requirements, and connections.
-
Convention: does every
part defhave at least onepartusage that instantiates it within the model? The language does not require this – an uninstantiated definition is perfectly legal, and library or catalogue definitions legitimately have no usage in the package that declares them. The review question is whether each unused definition is intentional (a library entry, work in progress) or dead weight that nothing verifies, connects, or simulates. (Chapter 3, Chapter 5) -
Convention: is every connection across a
port defgoverned by a namedinterface defthat specifies the connection contract? There is nothing to check on the conjugate side – everyport defautomatically carries a conjugated form (~PortDef); the language creates it for you. The heuristic is about the contract: when connections across a port have nointerface def, reviewers and tools have no stated agreement to check the two ends against. (Chapter 5, Chapter 6) -
Style: are load-bearing
connectusages typed by a namedconnection def? Anonymous connections are fully normative – they parse, carry flows, and are queryable like any other usage. Naming the definition is a reuse and traceability convention: a namedconnection defcan be instantiated again elsewhere and gives allocations and satisfaction links a stable target. (Chapter 6) -
Does every
partusage that can have more than one instance declare an explicit multiplicity? Omitting multiplicity implies exactly one instance. If a coffee machine can have between one and four heating elements, that constraint belongs in the model, not in a comment or document. (Chapter 5, Chapter 3) -
Convention: are all
importstatements explicit rather than wildcard in production packages? Wildcard imports (import SomePkg::*;) are convenient during drafting but create invisible name dependencies. In a reviewed package, each imported name should be traceable to a specific definition. (Chapter 4) -
Does every behavioral action that is assigned to a structural part have a corresponding
allocationlinking the action to that part? Without explicit allocation, the model declares behavior in the abstract but does not commit it to any physical or logical element. Reviewers and analysis tools need this link to check feasibility. (Chapter 6, Chapter 5) -
Convention: does the package structure separate domain definitions from contextual usages, with no definitions buried inside usage packages? Definitions mixed into context packages cannot be reused elsewhere and are harder to find during review. The canonical pattern is a definitions package imported by one or more context packages. (Chapter 4, Chapter 5)
Checklist 2: Requirements Review
Use this checklist during a requirements review, typically after the requirements package has been baselined and before the architecture team begins assigning satisfaction links. It verifies that every requirement is well-formed, bounded, and connected to the rest of the model.
-
Does every
requirementusage have asubjectmember declaring what the requirement constrains? A requirement without a subject is a floating assertion. The subject tells every reader – and every automated tool – exactly which element of the model must satisfy the requirement. (Chapter 10) -
Convention: does every leaf requirement usage (one with no child requirements) have at least one
require constraintor explicit constraint expression? A requirement with only adocannotation is legal, but it is an English sentence rather than a formal constraint. This convention asks leaf requirements to carry a verifiable condition that a constraint solver or test can evaluate. (Chapter 9, Chapter 10) -
Is every leaf requirement either satisfied by a
satisfyusage, or explicitly deferred with a documented rationale? An orphan requirement – one that exists in the model without a satisfaction link and without a deferral note – is an invisible gap. Reviewers cannot tell whether it was overlooked or intentionally left open. (Chapter 10) -
Does every
satisfyusage name a specific requirement usage rather than a requirement definition? Satisfying a definition claims to satisfy all possible instances of it, which is almost never the intent. The satisfaction link must point to the specific requirement usage in its context. (Chapter 10) -
Convention: is the requirement hierarchy no deeper than four levels? A hierarchy deeper than four levels usually indicates scope creep: requirements at the bottom level are design decisions, not stakeholder needs. Inspect any branch deeper than four levels and ask whether the lowest items belong in a design document instead. (Chapter 10, Chapter 15)
-
Convention: does every requirement have a
docannotation with rationale text explaining why the requirement exists? A requirement without rationale cannot be prioritized, traded off, or defended to a stakeholder. Rationale also guides verification: it tells you what evidence would actually satisfy the requirement. (Chapter 13, Chapter 10) -
Does every stakeholder concern raised during elicitation appear as a named
concernusage orrequirementin the model? A concern that was raised during review but never captured in the model is a hidden risk. Even concerns that do not yet have a formal requirement should appear asconcernusages so that the model tracks them explicitly. (Chapter 10)
Checklist 3: Behavior Review
Use this checklist during a behavior review, typically after action flows and state machines have been drafted. It verifies that behavioral elements are internally consistent, properly connected, and grounded in the structural model.
-
Does every
state defhave at least one entry transition and at least one exit condition? A state with no entry transition is unreachable. A state with no exit condition is a dead end. Either situation represents a logical error in the behavioral model. Check every state node, including nested parallel states. (Chapter 8, Chapter 7) -
Does every
transitionusage have an explicit guard, trigger, or both – with the only exception being the initial pseudo-transition? An unguarded, untriggered transition (other than the initial transition from the initial state) fires unconditionally on entry to its source state, which almost always indicates a modeling error. Verify the intent before approving. (Chapter 8) -
Are all
actionusages within a sequence model connected bysuccessionlinks, with no disconnected action nodes? An action node with no incoming succession and no outgoing succession is an island: it never executes in the sequence and will not appear in simulation or analysis. Every non-initial action needs at least one incoming succession link. (Chapter 7) -
Do all control-flow fork nodes have a corresponding join node that synchronizes the same branches? A fork without a join creates parallel branches that never converge. This may be intentional in some models, but it should be explicit: if there is no join, add a comment explaining why the branches are designed to diverge permanently. (Chapter 7)
-
Does every
accept actionandsend actionreference a port or flow definition that is declared in the structural model? Behavioral elements that send or receive across ports must reference the same port type used in the structural layer. A mismatch – for example, an action that sends aWaterFlowon a port typed forElectricalPower– is a semantic error that the structural review should have caught but the behavior review must confirm. (Chapter 6, Chapter 7) -
Style: does every
exhibitusage in an action definition reference a namedstate defrather than an anonymous inline state? Inline anonymous states are legal, but they cannot be referenced from other actions, tested in isolation, or carry documentation. Prefer a named state definition for any state that has more than trivial logic. (Chapter 8, Chapter 3) -
Do action parameter types match the item types declared on the ports or flows they read from or write to? A
performaction that reads aCoffeeGroundsitem but whose input parameter is typed asWaterwill pass a parser but fail during simulation or analysis. Parameter types must match the flow types at every boundary. (Chapter 7, Chapter 6)
Checklist 4: Verification Review
Use this checklist during a verification review, typically after the verification cases package has been drafted and before formal test planning begins. It verifies that every requirement has a path to objective evidence and that no verification case exists without a purpose.
-
Does every leaf requirement usage have at least one
verifyusage inside averification casethat references it? A requirement with no verification case cannot be closed. Requirements without objective evidence are assumptions. This item should be checked against the same list of leaf requirements identified in Checklist 2 item 3. (Chapter 10) -
Convention: is every
verification caseusage annotated with a verification method metadata tag – one of: analysis, test, inspection, or demonstration? Without a method tag, the verification case is unscheduled: you do not know who performs it, what evidence it produces, or when it runs. Method classification is also required for V&V plan traceability. (Chapter 13, Chapter 10) -
Does every
verification caseusage contain at least oneverifyusage that references a named requirement? A verification case that contains noverifyusages is a placeholder with no executable content. It may pass a parser but it contributes nothing to requirement closure. These orphan cases must either be populated or removed. (Chapter 10) -
Style: does every
assert constraintusage inside a verification case reference a namedconstraint defrather than an inline anonymous constraint? An anonymous inline constraint inside a verification case cannot be reused, named, or referenced from a test script or analysis tool. Named constraint definitions allow the same constraint to appear in both the requirement and the verification case, making the link explicit. (Chapter 9, Chapter 10) -
Convention: is every verification case annotated with a responsible stakeholder or team via a metadata usage? An unassigned verification case will not be executed. Ownership metadata can be as simple as a
#Responsible { team = "Systems Test"; }annotation; the convention asks that it be present so that test planning tools and project managers can schedule the work. (Chapter 13) -
Does the coverage matrix show every requirement at the leaf level mapped to at least one verification case? Individual checklist items verify individual connections. This item asks you to look at the whole picture: generate or inspect the requirement-to-verification-case coverage matrix and confirm no leaf requirement appears as uncovered. The Systems Modeling API supports this as a graph query. (Chapter 10, Appendix C)
-
Are verdict expressions in verification cases written using named
calculation defresults rather than literal boolean constants? A verification case whose verdict isassert true;is always passing and provides no information. Verdicts must evaluate observable conditions: parameter values, measured outputs, or constraint evaluations drawn from a named calculation. (Chapter 9, Chapter 10) -
Has a
view def <Milestone>ReviewView(carrying thefilter <viewCondition>;) plus a view usage (carryingexpose <package>::*;) been authored to scope the milestone review? A milestone review pulls a particular slice of the model — the requirements affected by this gate, the parts touched by this design phase, the verification cases due this iteration. Authoring that slice as a named view (rather than relying on each reviewer to remember which packages to inspect) makes the scope explicit, reviewable, and re-runnable for the next milestone. Thefilterclause lives in the view definition and narrows the slice to the relevant element kinds; theexpose ... ::*lives in the view usage (aview defbody does not admitexpose) and keeps the view fresh as the underlying packages grow. (Chapter 13: Metadata and Views)
Appendix J: Occurrences, Time Slices, and Snapshots
The chapters model a coffee machine: what it is made of, what it does, what it must satisfy. What they do not dwell on is that a machine, a brew, and a fault are all things that happen over time, and that SysML v2 has a specific family of constructs for saying so.
You can build a great deal without them. Reach for them when time itself becomes part of what you are modelling – phases of an operation, the state of something at an instant, or one particular real-world unit as opposed to the kind.
Occurrences
occurrence def <Name>;
occurrence <name> : <OccurrenceDef>;
occurrence def BrewCycle;
An occurrence is something that happens or exists over an interval of time.
occurrence is its kind keyword, and it works like every other kind keyword you
have met – occurrence def for the definition, occurrence for a usage
(SysML §7.9.2).
You have been using occurrences all along without naming them. Actions and states are occurrence usages of more specialized kinds: that is what lets a state have a duration and an action have a start and an end. This appendix is about the layer underneath them.
An occurrence usage may only be typed by an occurrence definition of some kind, or by a KerML class.
Time slices and snapshots
timeslice <name>; // equivalent to `timeslice occurrence <name>`
snapshot <name>; // equivalent to `snapshot occurrence <name>`
timeslice <kind> <name>; // e.g. `timeslice part activeSection`
occurrence def BrewCycle {
timeslice grinding;
timeslice heating;
timeslice extracting;
snapshot extractionStart;
}
Read it as: a brew cycle has three phases and one instant worth naming.
A time slice is a portion of an occurrence over an interval – the grinding
phase of a brew. A snapshot is a portion at a single instant – the moment
extraction begins. Both are written by putting the keyword immediately before the
kind keyword of the declaration, and both may stand in place of the kind
keyword, in which case they mean timeslice occurrence and snapshot occurrence
(SysML §7.9.3).
One rule to remember: a time slice or snapshot must be declared inside the body of an occurrence definition or usage. It is a portion of something, so there has to be a something.
The distinction is worth keeping straight because it is the difference between a duration and an instant. “While heating” is a time slice. “At the moment the pump started” is a snapshot. A constraint on a temperature rise belongs to the former; a constraint on a temperature reading belongs to the latter.
Individuals
individual def <Name> :> <OccurrenceDef>;
individual <kind> def <Name> :> <Def>;
part def Grinder;
individual part def Grinder_SN0417 :> Grinder;
occurrence def BrewCycle;
individual def BrewCycle_20260821_0730 :> BrewCycle;
An individual definition describes exactly one thing in the world, rather
than a kind of thing. Grinder is a kind; Grinder_SN0417 is the grinder on the
bench with that serial number. BrewCycle is a kind of operation; the second
declaration is the brew that ran at half past seven.
Like timeslice and snapshot, the keyword goes immediately before the kind
keyword, or in place of it (SysML §7.9.4).
A usage counts as an individual usage when its definition is an individual definition – you do not have to mark the usage. And a usage may not have more than one individual definition, which follows from what an individual is: two “exactly one thing” claims about the same usage would contradict each other.
This is the construct to reach for when the model has to talk about real units – a serial-numbered part, a specific test run, an incident that actually happened – rather than the design.
Event occurrences
occurrence def BrewCycle {
event occurrence pumpOn;
}
An event occurrence marks a point of interest within an occurrence
(SysML §7.9.5). It is what the from and to ends of a message name, which is
why Appendix G mentions them under flows: a
message runs between two events rather than between two features.
Where this shows up elsewhere
- Successions and the
HappensBeforeassociation order occurrences in time. Chapter 7 covers successions. - States and actions are occurrence usages, which is why they have durations and boundaries. Chapter 8, Chapter 7.
- The standard library’s
Occurrencespackage declares the machinery – includingHappensBefore, and thestartShot/endShot/snapshotsstructure that gives every occurrence its boundaries. Chapter 14.
Language Reference
This section is generated from the sysml-rs language pack: a machine-readable index of every SysML v2 / KerML language concept, one “card” per concept. The pack is an index over the normative sources, not an authority: every card points at the governing specification clause and paraphrases it — where a card and the specification disagree, the specification wins. Use these pages to find which clause governs a question, then read and cite that clause.
Implementation-support marks (parse / resolve / elaborate / execute) are machine-derived from test evidence in the sysml-rs repository, never hand-written: ✓ means a gate test passed for that axis at the current spec drop, ✗ means a reviewed known limitation, and unknown means no evidence either way — it never means “no”.
Generated from spec drop 2025-04 (327 cards, pack tree 44751e816e61e04c8d799cf5c1adacb6a987bb52073b87794f486eb35e71f8b5).
Categories
- Behavior (51 cards)
- Cases (15 cards)
- Connections (1 cards)
- Expressions (37 cards)
- Implementation Notes (10 cards)
- Standard Library (33 cards)
- Metadata (5 cards)
- Requirements (18 cards)
- State Machines (8 cards)
- Structure (65 cards)
- Validation Rules (76 cards)
- Views (8 cards)
Raw JSON for tools and agents
The pack itself ships with this book as static JSON under language-pack/: manifest.json (spec-drop identity and pinned source hashes), cards/<id>.json (one card per concept), and indexes/ (keywords.json term index, aliases.json alias → card id, dependencies.json one-hop expansion map, cards.jsonl the whole corpus as JSONL). On the published site these resolve as <book-url>/language-pack/manifest.json, <book-url>/language-pack/cards/<id>.json, and so on. The intended lookup pattern: find candidate cards via indexes/keywords.json or indexes/aliases.json, read cards/<id>.json, expand one hop via indexes/dependencies.json, then cite the card’s normative_clauses.
Regenerating
These pages and the raw JSON are generated artifacts — edit the generator, not the pages. From a sibling sysml-rs checkout:
# regenerate the pack (see tools/spec-index/README.md for source fetching)
cargo run -p spec-index -- language-pack
# re-render this section + refresh the shipped JSON (from the book repo)
./tools/render-language-pack.sh
Licensing and attribution
The pack is citation-only by design: no OMG specification prose is reproduced in any card, example, or page — summaries are original paraphrases, and normative content is referenced by document + clause locator. The grammar information (rule names, structure, keyword literals) is derived from the Xtext grammars of the SysML-v2 Pilot Implementation, which is licensed LGPL-3.0-or-later; this notice covers that derivation. Metamodel facets are derived from the OMG-published TTL vocabularies at pinned revisions. These generated pages and the shipped JSON carry the terms above, distinct from the CC-BY-4.0 license of this book’s prose chapters.
Behavior
Generated from the sysml-rs language pack — see the Language Reference index for provenance, licensing, and the raw JSON. 51 cards.
Behavior
KerML — kerml.behavior.behavior
A behavior is a classifier of things that happen over time — a specification of how a system acts. It is the KerML base of SysML action definitions and its steps order the occurrences it coordinates.
package P { behavior B; }
Normative clauses: KerML §7.4.7
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Function
KerML — kerml.behavior.function
A function is a behavior that produces a result by evaluating an expression over its parameters. It is the KerML base of SysML calculation definitions; a return parameter carries its result.
package P { function F; }
Normative clauses: KerML §7.4.8
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Interaction
KerML — kerml.behavior.interaction
An interaction is both a behavior and an association: a behavior that coordinates the things it connects, describing how they interact over time. It is the KerML base of item flows between participants.
package P { interaction I; }
Normative clauses: KerML §7.4.10
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Invariant
KerML — kerml.behavior.invariant
An invariant asserts that a boolean expression must always hold, written inv { expr } inside a constraint. It lowers to a distinct Invariant carrying the asserted boolean condition.
constraint def C { inv { 1 > 0 } }
Normative clauses: KerML §8.3.4.7.5
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Predicate
KerML — kerml.behavior.predicate
A predicate is a function whose result is a boolean — a named condition that evaluates to true or false. It is the KerML base of SysML constraint definitions.
package P { predicate Pr { true } }
Normative clauses: KerML §8.2.5.7.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Calc Result Is Expression Value
KerML — kerml.validation.calc-result-is-expression-value
A calculation returns, in its result parameter, the value of evaluating its result expression.
Normative clauses: KerML §7.4.8.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Clock Timeflow Constraint
KerML — kerml.validation.clock-timeflow-constraint
The currentTime of a Clock snapshot equals the TimeOf that snapshot relative to the clock: snapshots->forAll{TimeOf(s, thisClock) == s.currentTime}.
Normative clauses: KerML §9.2.12.2 (nearest heading)
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Durationof End Minus Start
KerML — kerml.validation.durationof-end-minus-start
DurationOf = TimeOf(endShot) − TimeOf(startShot).
Normative clauses: KerML §9.2.12.2 (nearest heading)
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Happens Just Before No Intervening
KerML — kerml.validation.happens-just-before-no-intervening
HappensJustBefore: no occurrence can exist in the gap between earlier and later.
Normative clauses: KerML §9.2.4.2 (nearest heading)
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Invocation Binds Arguments To Input Params
KerML — kerml.validation.invocation-binds-arguments-to-input-params
Input parameters bind to their corresponding argument values.
Normative clauses: KerML §7.4.9.1
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Local Clock Defaults To Universal Clock
KerML — kerml.validation.local-clock-defaults-to-universal-clock
An occurrence’s localClock defaults to Clocks::universalClock; suboccurrences inherit it.
Normative clauses: KerML §9.2.4.2 (nearest heading)
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Occurrence Has Lifetime Extent
KerML — kerml.validation.occurrence-has-lifetime-extent
An occurrence has an extent in time (lifetime) from start to end.
Normative clauses: KerML §9.2.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Timeof Continuity Constraint
KerML — kerml.validation.timeof-continuity-constraint
If A HappensJustBefore B, TimeOf(A.endShot) == TimeOf(B).
Normative clauses: KerML §9.2.12.2 (nearest heading)
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Timeof Ordering Constraint
KerML — kerml.validation.timeof-ordering-constraint
If A HappensBefore B, TimeOf(A.endShot) ≤ TimeOf(B).
Normative clauses: KerML §9.2.12.2 (nearest heading)
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Accept Action
SysML — sysml.behavior.accept-node
An accept action waits for an incoming event or message and binds its payload, so the action’s flow continues only once a matching occurrence is received. It is written with the accept keyword inside an action body.
package P { action def A { accept sig; } }
Normative clauses: SysML §7.17.8
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Action Definition
SysML — sysml.behavior.action-definition
An action definition defines a reusable kind of behavior that transforms inputs into outputs over time. Action usages instantiate it as steps within a larger behavior.
package P { action def Move; }
Normative clauses: SysML §8.3.17.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Action Usage
SysML — sysml.behavior.action-usage
An action usage is an occurrence of behavior that transforms inputs into outputs. It is typed by an action definition and can hold parameters and nested actions.
package P { action def Move; action m : Move; }
Normative clauses: SysML §8.3.17.4
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Assignment Action
SysML — sysml.behavior.assignment-node
An assignment action sets a feature to the value of an expression, mutating model state as the action flow proceeds. It is written with the assign keyword and the := operator.
package P { action def A { attribute x; assign x := 1; } }
Normative clauses: SysML §7.17.9
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Calculation Definition
SysML — sysml.behavior.calculation-definition
A calculation definition defines a reusable computation that maps inputs to a returned result. It is a behavior whose body is an expression; usages invoke it to compute values.
package P { calc def Sum { in a; in b; return a + b; } }
Normative clauses: SysML §8.3.19.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Calculation Usage
SysML — sysml.behavior.calculation-usage
A calculation usage is a typed occurrence of a calculation within a model. It is typed by a calculation definition and binds its inputs to produce a result.
package P { calc def Sum { in a; in b; return a + b; } calc s : Sum; }
Normative clauses: SysML §8.3.19.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Decision Node
SysML — sysml.behavior.decision-node
A decision node is a control node that routes flow down exactly one outgoing branch, chosen by guards on its successions. It is written with the decide keyword inside an action body.
package P { action def A { decide d; } }
Normative clauses: SysML §7.17.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Exhibit State Usage
SysML — sysml.behavior.exhibit-state
An exhibit state usage lets a part or other structure display a state machine defined elsewhere as its own behavior, without redefining it. It is written with the exhibit state keywords and references the state whose behavior is exhibited.
package P { state def SD; part def M { exhibit state st : SD; } }
Normative clauses: SysML §8.3.18.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
For Loop Action
SysML — sysml.behavior.for-loop-node
A for loop action iterates a body action over the elements of a sequence, binding a loop variable to each element in turn. It is written with the for keyword, a variable, and an in sequence.
package P { action def A { attribute nums; for i in nums { action b; } } }
Normative clauses: SysML §7.17.12
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Fork Node
SysML — sysml.behavior.fork-node
A fork node is a control node that splits action flow into concurrent branches, activating every outgoing succession at once. It is written with the fork keyword inside an action body.
package P { action def A { fork f; } }
Normative clauses: SysML §7.17.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Join Node
SysML — sysml.behavior.join-node
A join node is a control node that synchronizes concurrent branches, resuming flow only once every incoming succession has completed. It is written with the join keyword inside an action body.
package P { action def A { join j; } }
Normative clauses: SysML §7.17.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Merge Node
SysML — sysml.behavior.merge-node
A merge node is a control node that combines alternative branches back into a single flow, continuing when any one incoming succession arrives. It is written with the merge keyword inside an action body.
package P { action def A { merge m; } }
Normative clauses: SysML §7.17.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Message
SysML — sysml.behavior.message
A message (message ... from ... to ...) models a transfer between a source and target end without specifying how the payload is obtained or delivered. The spec models a message as a flow usage (the grammar rule Message returns FlowUsage), so it lowers to a FlowUsage — there is no distinct Message metaclass.
package P { part a; part b; message m from a to b; }
Normative clauses: SysML §7.16.1
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Message Event
SysML — sysml.behavior.message-event
A message event is one end of a message (message m from a to b; — each of a and b). The grammar rule MessageEvent returns EventOccurrenceUsage: each end lowers to an unnamed EventOccurrenceUsage owned by the message through a ParameterMembership, referencing the end feature via an owned ReferenceSubsetting — there is no distinct MessageEvent metaclass.
package P { part def C { part a; part b; message m from a to b; } }
Normative clauses: SysML §8.2.2.16
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Perform Action
SysML — sysml.behavior.perform-action
A perform action invokes an action defined elsewhere as a step in the current behavior, reusing that action’s definition. It is written with the perform keyword inside an action body.
package P { action def A { perform action pa; } }
Normative clauses: SysML §7.17.6
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Send Action
SysML — sysml.behavior.send-node
A send action dispatches a payload to a target so it can be accepted elsewhere. It is written with the send keyword and a to target inside an action body.
package P { action def A { action tgt; send sig to tgt; } }
Normative clauses: SysML §7.17.7
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
State Action Usage
SysML — sysml.behavior.state-action-usage
A state subaction (an entry, do, or exit action of a state) is modeled as an ActionUsage owned by the state through a StateSubactionMembership that carries the subaction kind (the grammar rule StateActionUsage returns ActionUsage). It lowers to an ActionUsage owned via a StateSubactionMembership — there is no distinct StateActionUsage metaclass.
package P { state def S { entry action e; do action d; exit action x; } }
Normative clauses: SysML §8.3.18.4
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
State Definition
SysML — sysml.behavior.state-definition
A state definition defines a reusable kind of state machine — a set of states and transitions the modelled system moves through. State usages instantiate it.
package P { state def SM { state s1; state s2; } }
Normative clauses: SysML §8.3.18.5
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
State Usage
SysML — sysml.behavior.state-usage
A state usage is an occurrence of a state in a state machine, optionally with entry, do, and exit behaviors. It is a stage the modelled system can occupy.
package P { state def SM { state s1; state s2; } }
Normative clauses: SysML §8.3.18.6
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Succession
SysML — sysml.behavior.succession
A succession orders behavior, requiring one action or step to complete before its successor begins. It connects a source occurrence to a target occurrence.
package P { action def A { action a1; action a2; first a1 then a2; } }
Normative clauses: SysML §8.3.13.6
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Succession Flow Usage
SysML — sysml.behavior.succession-flow-usage
A succession flow, written succession flow … from … to …, is a flow that also imposes a temporal succession between its ends. It lowers to a distinct SuccessionFlowUsage.
part def P { in item a; out item b; succession flow sf from a to b; }
Normative clauses: SysML §8.3.16.4
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Terminate Node
SysML — sysml.behavior.terminate-node
A terminate node (terminate) ends an occurrence during an action’s performance. It lowers to a distinct TerminateActionUsage — an ActionUsage specializing the Systems Library TerminateAction — whose terminatedOccurrence defaults to the performing occurrence.
package P { action def A { terminate; } }
Normative clauses: SysML §8.3.17.16
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Transition Usage
SysML — sysml.behavior.transition-usage
A transition usage specifies a change from a source state to a target state, optionally guarded by a condition and carrying trigger and effect behaviors. It is written with the transition keyword and a then target.
package P { state def TrafficLight { state red; state green; transition first red then green; } }
Normative clauses: SysML §8.3.18.9
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
While Loop Action
SysML — sysml.behavior.while-loop-node
A while loop action repeats a body action while a boolean condition holds, re-evaluating the condition before each iteration. It is written with the while keyword and a condition inside an action body.
package P { action def A { while true { action w; } } }
Normative clauses: SysML §7.17.12
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Calc Always Has Result Parameter
SysML — sysml.validation.calc-always-has-result-parameter
A calculation always has a result parameter (inherited if not owned); evaluation always yields a result.
Normative clauses: SysML §7.19.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Decision Node Exactly One Outgoing
SysML — sysml.validation.decision-node-exactly-one-outgoing
A decision routes to exactly one outgoing branch per performance.
Normative clauses: SysML §7.17.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
For Loop Iterates Over Sequence
SysML — sysml.validation.for-loop-iterates-over-sequence
A ForLoopAction assigns each successive value from seq to its var loop variable and performs body for each; internally implemented via a nested WhileLoopAction.
Normative clauses: SysML §8.4.13.10
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Fork Node Concurrent Fanout
SysML — sysml.validation.fork-node-concurrent-fanout
A fork orders itself before ALL outgoing targets (every branch activates).
Normative clauses: SysML §7.17.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
If Action Evaluates Test Then Branch
SysML — sysml.validation.if-action-evaluates-test-then-branch
An IfThenAction evaluates its ifTest; if true, performs thenClause; an IfThenElseAction additionally performs elseClause when ifTest is false.
Normative clauses: SysML §8.4.13.9
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Interpolate Returns Null Out Of Bounds
SysML — sysml.validation.interpolate-returns-null-out-of-bounds
Interpolate returns null (no extrapolation) for an out-of-bounds input.
Normative clauses: SysML §9.4.3.2 (nearest heading)
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Join Node Synchronize All Incoming
SysML — sysml.validation.join-node-synchronize-all-incoming
A join is ordered after ALL incoming sources complete.
Normative clauses: SysML §7.17.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Merge Node Any One Incoming
SysML — sysml.validation.merge-node-any-one-incoming
A merge fires once per exactly-one incoming control.
Normative clauses: SysML §7.17.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Mref Dimension Must Match Attribute
SysML — sysml.validation.mref-dimension-must-match-attribute
A supplied mRef must have the same quantity dimension as the attribute being bound/assigned/compared.
Normative clauses: SysML §9.8.9.1
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Quantity Arithmetic Dimension Rules
SysML — sysml.validation.quantity-arithmetic-dimension-rules
+/− require equal dimension; × multiplies dimensions; relational ops require same quantity type.
Normative clauses: SysML §9.8.9.1
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Sampled Function Must Be Monotonic
SysML — sysml.validation.sampled-function-must-be-monotonic
A SampledFunction’s domain values must be strictly increasing or decreasing.
Normative clauses: SysML §9.4.3.2 (nearest heading)
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Send Action Initiates Message Transfer
SysML — sysml.validation.send-action-initiates-message-transfer
A send initiates a MessageTransfer carrying the payload from the sender.
Normative clauses: SysML §8.4.13.5
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
While Loop Iterates While Test
SysML — sysml.validation.while-loop-iterates-while-test
A WhileLoopAction performs its body while whileTest evaluates to true and untilTest evaluates to false; terminates when whileTest is false or untilTest is true.
Normative clauses: SysML §8.4.13.10
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Cases
Generated from the sysml-rs language pack — see the Language Reference index for provenance, licensing, and the raw JSON. 15 cards.
Analysis Case
SysML — sysml.cases.analysis-case
An analysis case computes results from inputs to support a decision, typed by an analysis case definition. Its subject and bound features feed the analysis.
package P { analysis def AC; analysis ac : AC; }
Normative clauses: SysML §8.3.23.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Related: Analysis Case (Library Definition)
Analysis Case Definition
SysML — sysml.cases.analysis-case-definition
An analysis case definition is a case that computes an analytical result about its subject — a study, trade, or evaluation. It is written with the analysis def keywords.
package P { analysis def AnaD; }
Normative clauses: SysML §8.3.23.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Case Definition
SysML — sysml.cases.case-definition
A case definition is a process that produces a result while relating a subject to an objective — the common base of analysis, verification, and use cases. It is written with the case def keywords.
package P { case def CaseD; }
Normative clauses: SysML §8.3.22.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Case Usage
SysML — sysml.cases.case-usage
A case usage is a specific performed case, typed by a case definition, that carries out its process for a subject. It is written with the case keyword and a type.
package P { case def CaseD; case c : CaseD; }
Normative clauses: SysML §8.3.22.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Include Use Case
SysML — sysml.cases.include-use-case
An included use case invokes another use case as a step of the current one, reusing its behavior. It is written with the include use case keywords and lowers to a distinct include-use-case usage.
package P { use case def UC { include use case inc; } }
Normative clauses: SysML §8.3.25.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Objective
SysML — sysml.cases.objective
An objective states the requirement a case aims to satisfy — the goal that defines when the case has succeeded. It is written with the objective keyword inside a case and lowers to an objective membership carrying the requirement usage.
package P { case def CaseD { objective obj; } }
Normative clauses: SysML §8.3.22.4
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Requirement Verification
SysML — sysml.cases.requirement-verification
A requirement verification names the requirement a verification case is checking, tying the case’s verdict to that requirement. It is written with the verify keyword and lowers to a requirement-verification membership carrying the requirement usage.
package P { requirement def R; verification def VerD { verify requirement r : R; } }
Normative clauses: SysML §8.3.24.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Use Case Definition
SysML — sysml.cases.use-case-definition
A use case definition defines a reusable goal-oriented interaction between a subject system and its actors. Use case usages instantiate it against a specific subject.
package P { use case def Withdraw; }
Normative clauses: SysML §8.3.25.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Use Case Usage
SysML — sysml.cases.use-case-usage
A use case usage is a typed occurrence of a use case within a model, typed by a use case definition. It applies the interaction to a concrete subject and actors.
package P { use case def Withdraw; use case uc : Withdraw; }
Normative clauses: SysML §8.3.25.4
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Verification Case
SysML — sysml.cases.verification-case
A verification case checks that a system satisfies its requirements, producing a verdict from its objective and result. It verifies the requirements it references.
package P { verification def VC; verification vc : VC; }
Normative clauses: SysML §8.3.24.4
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Related: Verification Case (Library Definition)
Verification Case Definition
SysML — sysml.cases.verification-case-definition
A verification case definition is a case that checks whether a subject meets its requirements, producing a verdict. It is written with the verification def keywords.
package P { verification def VerD; }
Normative clauses: SysML §8.3.24.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Analysis Case Objective Bound To Result
SysML — sysml.validation.analysis-case-objective-bound-to-result
An analysis case’s objective subject is bound to the analysis result (not the case subject).
Normative clauses: SysML §7.23.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Case Has Subject And Objective
SysML — sysml.validation.case-has-subject-and-objective
A case has ≤1 subject (first input) and ≤1 objective.
Normative clauses: SysML §8.3.22.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Verdict Criteria Modeled Explicitly
SysML — sysml.validation.verdict-criteria-modeled-explicitly
The pass/fail criteria must be modeled explicitly in the case body; no implicit derivation.
Normative clauses: SysML §8.4.20.1
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Verdict Semantics
SysML — sysml.validation.verdict-semantics
Pass=subject determined to satisfy; Fail=determined not to; Inconclusive=determination could not be made; Error=error during verification.
Normative clauses: SysML §7.24.1
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Connections
Generated from the sysml-rs language pack — see the Language Reference index for provenance, licensing, and the raw JSON. 1 cards.
Port Usage Referential
SysML — sysml.validation.port-usage-referential
A port’s non-port nested/owned usages must be referential (non-composite).
Normative clauses: SysML §8.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Expressions
Generated from the sysml-rs language pack — see the Language Reference index for provenance, licensing, and the raw JSON. 37 cards.
Additive Operator
Expressions — kerml.expression.additive-operator
The additive operator groups expression operands with precedence level 13 of 16 (higher binds tighter); it is left-associative. Symbols: + -.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
And Operator
Expressions — kerml.expression.and-operator
The and operator groups expression operands with precedence level 6 of 16 (higher binds tighter); it is left-associative. Symbols: &.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Cast Operator
Expressions — kerml.expression.cast-operator
The cast operator groups expression operands with precedence level 10 of 16 (higher binds tighter); it is left-associative. Symbols: as.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
ClassificationTest Operator
Expressions — kerml.expression.classification-test-operator
The classificationtest operator groups expression operands with precedence level 8 of 16 (higher binds tighter); it is left-associative. Symbols: hastype istype @.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
ConditionalAnd Operator
Expressions — kerml.expression.conditional-and-operator
The conditionaland operator groups expression operands with precedence level 6 of 16 (higher binds tighter); it is left-associative. Symbols: and.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Conditional Operator
Expressions — kerml.expression.conditional-operator
The conditional operator groups expression operands with precedence level 1 of 16 (higher binds tighter); it is left-associative. Symbols: if.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
ConditionalOr Operator
Expressions — kerml.expression.conditional-or-operator
The conditionalor operator groups expression operands with precedence level 4 of 16 (higher binds tighter); it is left-associative. Symbols: or.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Constructor Expression
Expressions — kerml.expression.constructor
A constructor expression (new T(args)) instantiates a type, binding some or all of its features to the argument results. The new keyword is the discriminator: new Widget(size = 1) lowers to a distinct ConstructorExpression (a subclass of InstantiationExpression), whereas the un-prefixed Widget(size = 1) lowers to a plain InvocationExpression.
package P { part def Widget { attribute size; } part a { attribute x = new Widget(size = 1); } }
Normative clauses: KerML §8.3.4.8.3
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Equality Operator
Expressions — kerml.expression.equality-operator
The equality operator groups expression operands with precedence level 7 of 16 (higher binds tighter); it is left-associative. Symbols: == != === !==.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Exponentiation Operator
Expressions — kerml.expression.exponentiation-operator
The exponentiation operator groups expression operands with precedence level 15 of 16 (higher binds tighter); it is right-associative. Symbols: ** ^.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Feature Reference Expression
Expressions — kerml.expression.feature-reference
A feature reference expression names a feature to yield its value — the way one feature’s value is read into another expression. It is written as the referenced feature’s name.
package P { attribute y = 1; attribute x = y; }
Normative clauses: KerML §8.3.4.8.5
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Feature Value
KerML — kerml.expression.feature-value
A feature value binds a feature to the result of an expression, either as an initial value or a fixed binding. The value’s type must be compatible with the feature.
package P { attribute a = 5; }
Normative clauses: KerML §8.3.4.10.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Related: Calculation (Library Definition)
Implies Operator
Expressions — kerml.expression.implies-operator
The implies operator groups expression operands with precedence level 3 of 16 (higher binds tighter); it is left-associative. Symbols: implies.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Invocation Expression
Expressions — kerml.expression.invocation
An invocation expression applies a function or calculation to argument expressions, binding each argument to a parameter and producing a result. Arguments may be positional.
package P { calc def Add { in a; in b; return a + b; } part def Q { attribute r = Add(1, 2); } }
Normative clauses: KerML §8.3.4.8.8
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Boolean Literal
Expressions — kerml.expression.literal-boolean
A boolean literal is one of the two truth constants, true or false.
package P { attribute x = true; }
Normative clauses: KerML §8.3.4.8.9
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Infinity Literal
Expressions — kerml.expression.literal-infinity
The infinity literal, written *, denotes an unbounded value — most often the upper bound of an unbounded multiplicity.
package P { attribute x = *; }
Normative clauses: KerML §8.3.4.8.11
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Integer Literal
Expressions — kerml.expression.literal-integer
An integer literal is a whole-number constant expression, such as 42. It is the simplest literal value a feature can be given.
package P { attribute x = 5; }
Normative clauses: KerML §8.3.4.8.12
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Real Literal
Expressions — kerml.expression.literal-real
A real literal is a rational-number constant expression written with a decimal point, such as 3.14. Its grammar rule is LiteralReal; it lowers to the engine’s LiteralRational kind.
package P { attribute x = 3.14; }
Normative clauses: KerML §8.3.4.8.13
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
String Literal
Expressions — kerml.expression.literal-string
A string literal is a text constant expression enclosed in double quotes, such as “hello”.
package P { attribute x = "hi"; }
Normative clauses: KerML §8.3.4.8.14
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
MetaCast Operator
Expressions — kerml.expression.meta-cast-operator
The metacast operator groups expression operands with precedence level 10 of 16 (higher binds tighter); it is left-associative. Symbols: meta.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
MetaClassificationTest Operator
Expressions — kerml.expression.meta-classification-test-operator
The metaclassificationtest operator groups expression operands with precedence level 9 of 16 (higher binds tighter); it is left-associative. Symbols: @@.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Metadata Access Expression
Expressions — kerml.expression.metadata-access
A metadata access expression, written element.metadata, yields the metadata annotations attached to an element as a value. It lowers to a distinct MetadataAccessExpression.
metadata def M; part def P { attribute a = P.metadata; }
Normative clauses: KerML §8.3.4.8.15
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Multiplicative Operator
Expressions — kerml.expression.multiplicative-operator
The multiplicative operator groups expression operands with precedence level 14 of 16 (higher binds tighter); it is left-associative. Symbols: * / %.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
NullCoalescing Operator
Expressions — kerml.expression.null-coalescing-operator
The nullcoalescing operator groups expression operands with precedence level 2 of 16 (higher binds tighter); it is left-associative. Symbols: ??.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Null Expression
Expressions — kerml.expression.null-expression
A null expression, written null, denotes the absence of a value — an empty result with no elements.
package P { attribute x = null; }
Normative clauses: KerML §8.3.4.8.16
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Or Operator
Expressions — kerml.expression.or-operator
The or operator groups expression operands with precedence level 4 of 16 (higher binds tighter); it is left-associative. Symbols: |.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Relational Operator
Expressions — kerml.expression.relational-operator
The relational operator groups expression operands with precedence level 11 of 16 (higher binds tighter); it is left-associative. Symbols: < > <= >=.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Unary Operator
Expressions — kerml.expression.unary-operator
The unary operator groups expression operands with precedence level 16 of 16 (higher binds tighter); it is a prefix unary operator. Symbols: + - ~ not.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Xor Operator
Expressions — kerml.expression.xor-operator
The xor operator groups expression operands with precedence level 5 of 16 (higher binds tighter); it is left-associative. Symbols: xor.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Constraint Result Boolean
KerML — kerml.validation.constraint-result-boolean
A constraint/predicate produces exactly one Boolean result; a non-Boolean result is not a valid verdict.
Normative clauses: KerML §7.4.8
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Core Operator Semantics
KerML — kerml.validation.core-operator-semantics
Constraint (Boolean) expressions evaluate comparison/logical/arithmetic operators per KerML expression semantics.
Normative clauses: KerML §7.4.8
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Feature Ref Resolves To Bound Value
KerML — kerml.validation.feature-ref-resolves-to-bound-value
A feature reference in a constraint evaluates to the value bound to that feature.
Normative clauses: KerML §8.3.4.8.5
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Unbound Feature Yields Inconclusive Not False
KerML — kerml.validation.unbound-feature-yields-inconclusive-not-false
An unresolved feature reference evaluates to the empty list; the result is not false. The behavior of an ordering comparison over an empty operand is spec-silent.
Normative clauses: KerML §8.3.4.8.5
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Assert Constraint Must Be True
SysML — sysml.validation.assert-constraint-must-be-true
A non-negated assert constraint asserts its result is true at all times; a false result is a logical inconsistency the tool flags.
Normative clauses: SysML §7.20
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Constraint Satisfied Iff True
SysML — sysml.validation.constraint-satisfied-iff-true
A constraint usage is satisfied iff its expression evaluates to true, and violated otherwise.
Normative clauses: SysML §7.20
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Constraint Usage Discovered
SysML — sysml.validation.constraint-usage-discovered
A ConstraintUsage and an AssertConstraintUsage are both surfaced as evaluable constraints.
Normative clauses: SysML §7.20
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Negated Assert Must Be False
SysML — sysml.validation.negated-assert-must-be-false
A negated assert not constraint asserts its result is false; a true result is the inconsistency.
Normative clauses: SysML §7.20
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Implementation Notes
Generated from the sysml-rs language pack — see the Language Reference index for provenance, licensing, and the raw JSON. 10 cards.
constructor expression does not lower to a distinct kind
Tooling — tooling.implementation.constructor-expression-generic-lowering
A constructor expression (Type(args)) parses cleanly but lowers to a generic InvocationExpression rather than a distinct ConstructorExpression, so it is not distinguished from an ordinary invocation. An implementation limitation, not a language restriction; the concept exists in the grammar.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Invocation Expression
if / terminate action nodes do not lower to distinct node kinds
Tooling — tooling.implementation.control-node-if-terminate-generic-lowering
The if control node and the terminate action node parse cleanly, but the lowering does not materialize their distinct IfActionUsage / TerminateActionUsage kinds — an if conditional succession lowers to a generic TransitionUsage and terminate does not surface a terminate node. An implementation limitation, not a language restriction; the grammar declares both node kinds.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Transition Usage, Action Usage
enum members do not lower to a distinct EnumerationUsage
Tooling — tooling.implementation.enumeration-usage-not-distinct
Members declared with enum inside an enum def parse cleanly, but the lowering produces no model element for them at all — not merely a generic usage in place of a distinct EnumerationUsage, but zero elements — so enumerated-value membership is not modelled in any form. This is an implementation limitation, not a language restriction — the concept exists in the grammar.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Enumeration Definition
message / message-event do not lower to distinct kinds
Tooling — tooling.implementation.message-generic-lowering
A message (and its message event) parses cleanly but lowers to a generic FlowUsage / EventOccurrenceUsage rather than a distinct Message / MessageEvent, so the message’s payload-transfer semantics are not modelled as such. An implementation limitation, not a language restriction; the concepts exist in the grammar.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Flow Connection, Event Occurrence Usage
individual / portion occurrence prefixes do not lower to distinct kinds
Tooling — tooling.implementation.occurrence-prefix-generic-lowering
The occurrence prefixes individual (individual def/individual), portion (portion), snapshot and timeslice parse but do not lower to their distinct occurrence element kinds — individual def in particular is not recognised as an OccurrenceDefinition marked individual, and the usage prefixes drop to a generic ReferenceUsage/OccurrenceUsage without their individual/portion role. An implementation limitation, not a language restriction; the concepts exist in the grammar.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Occurrence Definition, Occurrence Usage
prefixed individual|variation <kind> def and ref <keyword-usage> forms misparse
Tooling — tooling.implementation.prefixed-def-and-ref-keyword-usage-misparse
Two LR(1) lookahead splits: (a) individual part def / individual occurrence def / variation part def misparse into a standard_usage plus a feature_declaration — the usage-vs-definition choice must be made on the prefix+keyword lookahead but the forms diverge one token later at def; (b) ref enum / ref port (any ref <keyword-usage>) splits into an empty standard_usage plus the keyword usage. Implementation limitations, not language restrictions — both forms are normative (SysML.xtext OccurrenceDefinitionPrefix:800-806 / IndividualDefinition:813-817 / DefinitionPrefix variation, and RefPrefix on usages). The bare forms (individual def X;, standalone enum e;, port p;) all parse and lower.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Occurrence Definition, Part Definition, Enumeration Definition, Port Usage
membership-wrapped role usages under-resolve their type refs nondeterministically
Tooling — tooling.implementation.role-usage-resolution-nondeterminism
Membership-wrapped role usages once stamped their typing/reference target as a string prop on the membership (no lowered intermediate usage), so the resolver’s pass-1 loop hit a _ => {} no-op: the target was never resolved and a missing one never counted as unresolved — a silent drop (subject s : Missing gave no diagnostic), with nondeterminism from a runtime fallback scanning a HashMap in hash order. FIXED. Subject/objective: pass 1 routes them through resolve_feature_typing (resolves in the membership’s owning scope, stamps a type Ref, fail-hards a dangling target). assume/require constraint and framed concern reference forms: the parser mints the membership’s owned ConstraintUsage and hangs the grammar’s relationship on it — a ReferenceSubsetting for the bare-name form (SysML.xtext:448), a FeatureTyping for the : Def form — and referencedConstraint derives from that (SysML-vocab.ttl:2576), the resolver fail-harding both through the standard path. actor/stakeholder were always unaffected (they lower a real FeatureTyping).
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Subject, Actor, Stakeholder, Framed Concern, Objective
state effect / state action / trigger subactions do not lower to distinct kinds
Tooling — tooling.implementation.state-subaction-generic-lowering
A state effect behavior, a state action usage and a transition trigger action parse cleanly but lower to a generic ActionUsage / AcceptActionUsage rather than their distinct EffectBehaviorUsage / StateActionUsage / TriggerAction kinds — the state-subaction and trigger roles are not modelled as distinct usages. An implementation limitation, not a language restriction; the concepts exist in the grammar.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Exhibit State Usage, Accept Action
TextualRepresentation lowers to a generic ReferenceUsage
Tooling — tooling.implementation.textual-representation-generic-lowering
A KerML TextualRepresentation annotating element (a rep with a language string and a body) parses cleanly, but the tree-sitter lowering produces a generic ReferenceUsage rather than a distinct TextualRepresentation element, so the representation’s language and body are not modelled as such. This is an implementation limitation, not a language restriction — the concept exists in the grammar.
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Reference Usage
type-relationship operators do not materialize a distinct relationship kind
Tooling — tooling.implementation.type-relationship-fragment-generic-lowering
The type-relationship operators unioning (unions), intersecting (intersects), differencing (differences), disjoining (disjoint from), feature-inversion (inverse of), type-featuring (featured by) and conjugation (conjugate/~ on a type) parse cleanly, but the lowering does not produce their distinct Unioning/Intersecting/Differencing/Disjoining/ FeatureInverting/TypeFeaturing/Conjugation relationship elements — the participating types are recorded without the specialization relationship being modelled as such. An implementation limitation, not a language restriction; the concepts exist in the grammar. (The ~ port-conjugation form on ports IS modelled — see the conjugated-port-definition card.)
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Specialization
Standard Library
Generated from the sysml-rs language pack — see the Language Reference index for provenance, licensing, and the raw JSON. 33 cards.
Boolean Evaluation
KerML — kerml.library.boolean-evaluation
The Kernel Semantic Library predicate that is the most general class of Boolean-valued evaluations (a specialization of Evaluation). It is the base that the Systems Library ConstraintCheck specializes.
Normative clauses: KerML §9.2.6
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Constraint Check
FlowTransfer
KerML — kerml.library.flow-transfer
The concrete Transfer that names the source output feature and the target input feature the payload flows between — what an ordinary structural flow connection lowers to. Carries move/push semantics.
Normative clauses: KerML §9.2.7
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Transfer, MessageTransfer
HappensBefore
KerML — kerml.library.happens-before
The Kernel association asserting that one occurrence completely finishes before another begins, with no overlap in time — the semantic backbone of succession (then) ordering.
Normative clauses: KerML §9.2.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Occurrence, HappensDuring
HappensDuring
KerML — kerml.library.happens-during
The Kernel association asserting that one occurrence’s whole time interval falls inside another’s — used for containment timing, e.g. a substate being active during its enclosing state.
Normative clauses: KerML §9.2.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Occurrence, HappensBefore
head (Sequence Function)
KerML — kerml.library.head
The Kernel sequence function returning the first element of an ordered collection (equivalent to indexing position 1), or nothing when it is empty.
Normative clauses: KerML §9.4.14
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: tail (Sequence Function), last (Sequence Function)
includes (Sequence Function)
KerML — kerml.library.includes
The Kernel sequence predicate that is true when every element of one collection is also present in another — a membership/containment test.
Normative clauses: KerML §9.4.14
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: size (Sequence Function)
isEmpty (Sequence Function)
KerML — kerml.library.is-empty
The Kernel sequence predicate that is true exactly when a collection contains no elements. Complementary to notEmpty.
Normative clauses: KerML §9.4.14
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: notEmpty (Sequence Function), size (Sequence Function)
last (Sequence Function)
KerML — kerml.library.last
The Kernel sequence function returning the final element of an ordered collection, or nothing when it is empty.
Normative clauses: KerML §9.4.14
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: head (Sequence Function)
MessageTransfer
KerML — kerml.library.message-transfer
The Transfer variant that carries a payload with no named source-output or target-input feature — the transfer underlying send/accept action semantics. It is disjoint from FlowTransfer.
Normative clauses: KerML §9.2.7
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Transfer, FlowTransfer
notEmpty (Sequence Function)
KerML — kerml.library.not-empty
The Kernel sequence predicate that is true when a collection holds at least one element — the negation of isEmpty.
Normative clauses: KerML §9.4.14
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: isEmpty (Sequence Function)
Occurrence
KerML — kerml.library.occurrence
The Kernel root classifier for anything with identity that exists or happens across time and space. Actions, states, and individual parts ultimately specialize it; it anchors the timing (start/end snapshots) and transfer features every temporal element uses.
Normative clauses: KerML §9.2.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: HappensBefore, HappensDuring, Transfer
max (Scalar Function)
KerML — kerml.library.scalar-max
The Kernel scalar function returning whichever of its two ordered scalar operands is the greater. Invoked as max(a, b) in expressions.
Normative clauses: KerML §9.4.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: min (Scalar Function)
min (Scalar Function)
KerML — kerml.library.scalar-min
The Kernel scalar function returning whichever of its two ordered scalar operands is the lesser. Invoked as min(a, b) in expressions.
Normative clauses: KerML §9.4.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: max (Scalar Function)
size (Sequence Function)
KerML — kerml.library.size
The Kernel sequence function counting how many elements a collection holds, returning a Natural. col->size() is the idiomatic length query.
Normative clauses: KerML §9.4.14
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: includes (Sequence Function), isEmpty (Sequence Function)
State Performance
KerML — kerml.library.state-performance
The Kernel Semantic Library performance of being in a state (a specialization of DecisionPerformance) with entry/do/exit substeps. On entry, the transfer/event that triggered the entry is recorded on the state’s performance via its incomingTransitionTrigger feature.
Normative clauses: KerML §9.2.11
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: State Action (Library Definition)
tail (Sequence Function)
KerML — kerml.library.tail
The Kernel sequence function returning every element of an ordered collection except the first — the complement of head, used for recursive traversal.
Normative clauses: KerML §9.4.14
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: head (Sequence Function)
Transfer
KerML — kerml.library.transfer
The Kernel interaction that moves a payload from a source occurrence to a target occurrence. It is the abstract base every flow and message transfer specializes; a transfer may be instantaneous or take time.
Normative clauses: KerML §9.2.7
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: FlowTransfer, MessageTransfer, Occurrence
Analysis Case (Library Definition)
SysML — sysml.library.analysis-case
The abstract library base of all analysis cases (a specialization of Case). It carries out an evaluation over its subject, producing a result rather than a pass/fail verdict.
Normative clauses: SysML §7.23
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Case (Library Definition), Analysis Case
Calculation (Library Definition)
SysML — sysml.library.calculation
The abstract library base of all calculations (a specialization of Action and the Kernel Evaluation). A CalculationUsage evaluates an expression over its parameters and returns a result; when an argument is omitted, the declared parameter’s default value (a KerML FeatureValue) is used.
Normative clauses: SysML §7.19
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Feature Value
Case (Library Definition)
SysML — sysml.library.case
The abstract library base of all cases (a specialization of Calculation). A Case has a subject under investigation, actor parts, an objective expressed as a RequirementCheck, and a result that should satisfy that objective.
Normative clauses: SysML §7.22
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Requirement Check, Analysis Case (Library Definition), Verification Case (Library Definition)
Constraint Check
SysML — sysml.library.constraint-check
The abstract library base of all constraint definitions (a specialization of the Kernel BooleanEvaluation predicate). A ConstraintCheck evaluates to a Boolean; asserted and negated constraint checks partition it into the true/false evaluation subsets.
Normative clauses: SysML §7.20
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Boolean Evaluation, Requirement Check, Constraint Definition
DurationValue (ISQ Base Quantity)
SysML — sysml.library.duration-value
The ISQ base quantity kind for duration/time (dimension T). A DurationValue is a scalar quantity measured in a DurationUnit such as second.
Normative clauses: SysML §9.8.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: second (SI Unit)
kilogram (SI Unit)
SysML — sysml.library.kilogram
The SI base unit of mass (short name kg), typing MassValue quantities. Defined by applying the kilo prefix to the gram.
Normative clauses: SysML §9.8.6
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: MassValue (ISQ Base Quantity)
LengthValue (ISQ Base Quantity)
SysML — sysml.library.length-value
The ISQ base quantity kind for length (dimension L). A LengthValue is a scalar quantity measured in a LengthUnit such as metre.
Normative clauses: SysML §9.8.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: metre (SI Unit)
MassValue (ISQ Base Quantity)
SysML — sysml.library.mass-value
The ISQ base quantity kind for mass (dimension M). A MassValue is a scalar quantity measured in a MassUnit such as kilogram.
Normative clauses: SysML §9.8.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: kilogram (SI Unit)
metre (SI Unit)
SysML — sysml.library.metre
The SI base unit of length (short name m), typing LengthValue quantities. Imported from the SI library and composed into derived units such as newton.
Normative clauses: SysML §9.8.6
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: LengthValue (ISQ Base Quantity)
Pass If
SysML — sysml.library.pass-if
The library calculation mapping a Boolean to a VerdictKind: it returns VerdictKind::pass when its isPassing argument is true, otherwise VerdictKind::fail.
Normative clauses: SysML §7.24
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Verdict Kind
Requirement Check
SysML — sysml.library.requirement-check
The abstract library base of all requirement definitions (a specialization of RequirementConstraintCheck). It checks whether its subject satisfies the required constraints, given that all assumptions hold: its result is allTrue(assumptions) implies allTrue(constraints).
Normative clauses: SysML §7.21
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Case (Library Definition), Constraint Check, Requirement Definition
second (SI Unit)
SysML — sysml.library.second
The SI base unit of time/duration (short name s), typing DurationValue quantities. The reference unit for rates and time-based dynamics.
Normative clauses: SysML §9.8.6
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: DurationValue (ISQ Base Quantity)
State Action (Library Definition)
SysML — sysml.library.state-action
The abstract library base of all state usages (a specialization of Action and the Kernel StatePerformance). Its mutually-exclusive substates are sequenced by stateSequencing successions: with N exclusive substates there are exactly N-1 such successions.
Normative clauses: SysML §7.18
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: State Performance
Verdict Kind
SysML — sysml.library.verdict-kind
The library enumeration of the possible results of a verification case: pass, fail, inconclusive, and error. A VerificationCase returns a VerdictKind as its result.
Normative clauses: SysML §7.24
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Verification Case (Library Definition), Pass If
Verification Case (Library Definition)
SysML — sysml.library.verification-case
The abstract library base of all verification cases (a specialization of Case). It returns a VerdictKind verdict and records the RequirementChecks of the requirements being verified in its objective.
Normative clauses: SysML §7.24
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Verification Case, Case (Library Definition), Verdict Kind, Verification Method Kind
Verification Method Kind
SysML — sysml.library.verification-method-kind
The library enumeration of the standard methods by which verification can be carried out: inspect, analyze, demo, and test. Used via the VerificationMethod metadata annotating a verification case or action.
Normative clauses: SysML §7.24
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Verification Case (Library Definition)
Metadata
Generated from the sysml-rs language pack — see the Language Reference index for provenance, licensing, and the raw JSON. 5 cards.
Comment
KerML — kerml.metadata.comment
A comment carries free-form explanatory text about model elements. Its body is a note; an optional about list names the elements it annotates, and an optional locale tags the natural language.
package P { comment About /* explains the package */ part def Widget; }
Normative clauses: KerML §8.3.2.3.4
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Documentation
KerML — kerml.metadata.documentation
Documentation is comment text owned by the element it documents, giving that element a human-readable description. Its body is required; an optional locale tags the natural language.
package P { part def Widget { doc /* the primary component */ } }
Normative clauses: KerML §8.3.2.3.5
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Textual Representation
KerML — kerml.metadata.textual-representation
A textual representation (rep <name> language "L" /* body */, or the unnamed language "L" /* body */) is an AnnotatingElement whose comment body represents its owning element in a named language. It lowers to a distinct TextualRepresentation carrying the language string and the body text; the grammar admits it inside constraint/requirement bodies.
package P { constraint c { rep r language "html" /* <b>hi</b> */ } }
Normative clauses: KerML §8.3.2.3.6
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Metadata Definition
SysML — sysml.metadata.metadata-definition
A metadata definition defines a kind of annotation that carries structured data about model elements — semantic tags a tool or process can read. Usages of it attach that data to the elements they annotate.
package P { metadata def Priority; }
Normative clauses: SysML §8.3.27.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Metadata Usage
SysML — sysml.metadata.metadata-usage
A metadata usage attaches an instance of a metadata definition to one or more model elements. The prefix form @Name annotates the element it precedes with metadata of that kind.
package P { metadata def Priority; part def Widget { @Priority; } }
Normative clauses: SysML §8.3.27.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Requirements
Generated from the sysml-rs language pack — see the Language Reference index for provenance, licensing, and the raw JSON. 18 cards.
Actor
SysML — sysml.requirements.actor
An actor is an external party — a person, system, or role — that interacts with the subject of a requirement or case. It is written with the actor keyword and lowers to an actor membership carrying the actor part.
package P { part def Driver; requirement def R { actor a : Driver; } }
Normative clauses: SysML §8.3.21.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Assert Constraint
SysML — sysml.requirements.assert-constraint
An asserted constraint states a boolean condition that is claimed to hold for the enclosing element — it is taken as true rather than required or assumed. It is written with the assert keyword and lowers to a distinct assert-constraint usage.
package P { requirement def R { assert constraint ac { true } } }
Normative clauses: SysML §8.3.20.2
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Concern Definition
SysML — sysml.requirements.concern-definition
A concern definition defines a matter of interest to stakeholders that a system must address — a specialized requirement definition. Viewpoints frame concerns; views address them.
package P { concern def Safety { subject s; } }
Normative clauses: SysML §8.3.21.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Concern Usage
SysML — sysml.requirements.concern-usage
A concern usage is a typed occurrence of a concern, typed by a concern definition. It raises the concern where it applies, for a viewpoint to frame.
package P { concern def Safety { subject s; } concern cn : Safety; }
Normative clauses: SysML §8.3.21.4
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Constraint Definition
SysML — sysml.requirements.constraint-definition
A constraint definition defines a reusable boolean condition over parameters. Constraint usages assert it must hold, backing requirements and checks.
package P { constraint def Positive { in x; x > 0 } }
Normative clauses: SysML §8.3.20.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Related: Constraint Check
Constraint Usage
SysML — sysml.requirements.constraint-usage
A constraint usage asserts that a boolean condition holds for a model. It is typed by a constraint definition and evaluates to true or false.
package P { constraint def Positive { in x; x > 0 } constraint c : Positive; }
Normative clauses: SysML §8.3.20.4
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Framed Concern
SysML — sysml.requirements.framed-concern
A framed concern brings a concern into a requirement as something the requirement addresses — it frames the concern within the requirement’s scope. It is written with the frame keyword and lowers to a framed-concern membership carrying the concern usage.
package P { concern def Cn; requirement def R { frame concern fc : Cn; } }
Normative clauses: SysML §8.3.21.5
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Requirement Constraint (assume / require)
SysML — sysml.requirements.requirement-constraint
A requirement constraint is a boolean condition that a requirement either assumes as a precondition or requires as an obligation. It is written with the assume or require keyword and lowers to a requirement-constraint membership recording which role it plays.
package P { requirement def R { assume constraint ac { true } require constraint rc { true } } }
Normative clauses: SysML §8.3.21.7
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Requirement Definition
SysML — sysml.requirements.requirement-definition
A requirement definition states a condition a system must meet, over a subject and optional constraints. A requirement has at most one subject.
package P { requirement def R { subject s; } }
Normative clauses: SysML §8.3.21.8
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Related: Requirement Check
Requirement Usage
SysML — sysml.requirements.requirement-usage
A requirement usage is a typed occurrence of a requirement within a model, typed by a requirement definition. It states a condition an element must satisfy, nested where it applies.
package P { requirement def Rq { subject s; } requirement rq : Rq; }
Normative clauses: SysML §8.3.21.9
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Requirement Satisfaction
SysML — sysml.requirements.satisfaction
A satisfy relationship asserts that a subject element meets a requirement. It links the satisfying element to the requirement it fulfils.
package P { requirement def R { subject s; } part def Sys; part sys : Sys; satisfy R by sys; }
Normative clauses: SysML §8.3.21.10
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Stakeholder
SysML — sysml.requirements.stakeholder
A stakeholder is a party with an interest in a requirement’s satisfaction or a concern being addressed. It is written with the stakeholder keyword and lowers to a stakeholder membership carrying the stakeholder part.
package P { part def Owner; requirement def R { stakeholder sh : Owner; } }
Normative clauses: SysML §8.3.21.12
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Subject
SysML — sysml.requirements.subject
A subject binds a requirement, case, or concern to the thing it is about — the system or element under consideration. It is written with the subject keyword and lowers to a subject membership carrying the subject parameter.
package P { part def Car; requirement def R { subject s : Car; } }
Normative clauses: SysML §8.3.21.11
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Negated Satisfy Requires Not Satisfied
SysML — sysml.validation.negated-satisfy-requires-not-satisfied
A negated satisfy usage asserts the requirement evaluates to false; verdict inverts.
Normative clauses: SysML §7.21.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Requirement Check Result Is Boolean
SysML — sysml.validation.requirement-check-result-is-boolean
Every RequirementCheck result is Boolean.
Normative clauses: SysML §8.4.17.1
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Requirement Is Constraint Satisfied Iff True
SysML — sysml.validation.requirement-is-constraint-satisfied-iff-true
A requirement is a kind of constraint, satisfied iff it evaluates to true.
Normative clauses: SysML §7.21.1
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Requirement Result Is Assumption Implies Required
SysML — sysml.validation.requirement-result-is-assumption-implies-required
Effective result = allTrue(assumptions) implies allTrue(constraints); required constraints checked only when assumptions hold.
Normative clauses: SysML §7.21.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Requirement Subject Must Be First Parameter
SysML — sysml.validation.requirement-subject-must-be-first-parameter
The subject parameter must be the requirement’s first input.
Normative clauses: SysML §8.3.21
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
State Machines
Generated from the sysml-rs language pack — see the Language Reference index for provenance, licensing, and the raw JSON. 8 cards.
At Most One Transition Fires Per Trigger
KerML — kerml.validation.at-most-one-transition-fires-per-trigger
At most one outgoing transition fires per trigger (via accepted[0..1] + isDispatch).
Normative clauses: KerML §9.2.11.1
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
At Most One Each Subaction Kind
SysML — sysml.validation.at-most-one-each-subaction-kind
≤1 entry, ≤1 do, ≤1 exit per state.
Normative clauses: SysML §8.3.18.5
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Entry Do Exit Ordering
SysML — sysml.validation.entry-do-exit-ordering
Entry completes → do starts and runs while active → on exit, do interrupted, exit runs to completion.
Normative clauses: SysML §7.18.1
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Initial State Via Entry Succession
SysML — sysml.validation.initial-state-via-entry-succession
The initial substate is the target of a succession from the entry action.
Normative clauses: SysML §7.18.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
No Transition On Unmatched Event
SysML — sysml.validation.no-transition-on-unmatched-event
An event matching no outgoing transition of the current state leaves the machine in place (no spurious firing).
Normative clauses: SysML §7.18.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Transition Firing Order Exit Effect Entry
SysML — sysml.validation.transition-firing-order-exit-effect-entry
Firing sequence: interrupt source do → source exit → transition effect → target entry → target do.
Normative clauses: SysML §7.18.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Transition Guard Boolean
SysML — sysml.validation.transition-guard-boolean
A transition guard is a Boolean[1..1] expression that must be true for the transition to occur.
Normative clauses: SysML §7.18.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Transition Selection
SysML — sysml.validation.transition-selection
An accepted event-triggered transition fires and moves the machine to its named target state.
Normative clauses: SysML §7.18.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Structure
Generated from the sysml-rs language pack — see the Language Reference index for provenance, licensing, and the raw JSON. 65 cards.
Association
KerML — kerml.structure.association
An association is a classifier of links between things, relating two or more end features. It is the KerML base of SysML connection definitions; its ends name the related types.
package P { class C; assoc A { end a : C; end b : C; } }
Normative clauses: KerML §7.4.5
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Association Structure
KerML — kerml.structure.association-structure
An association structure is both an association and a structure: a link that itself carries structural features, so the link between things is a structured object in its own right.
package P { class C; assoc struct AS { end a : C; end b : C; } }
Normative clauses: KerML §7.4.5.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Binding Connector
KerML — kerml.structure.binding-connector
A binding connector asserts that its two ends are the same thing — a binding that forces the connected features to have identical values. It is written with the equals form.
package P { class C; feature x : C; binding b = x; }
Normative clauses: KerML §8.2.5.5.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Class
KerML — kerml.structure.class
A class is a classifier of occurrences — things that exist over time and space with intrinsic identity. It is the KerML base from which SysML parts and other occurrence-typed constructs derive.
package P { class C; }
Normative clauses: KerML §7.4.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Classifier
KerML — kerml.structure.classifier
A classifier is a type that classifies individual things by their intrinsic identity, as opposed to a feature that classifies things by their role. Classes, structures, and behaviors are all classifiers.
package P { classifier C; }
Normative clauses: KerML §7.3.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Classifier Conjugation
KerML — kerml.structure.classifier-conjugation
A classifier conjugation (classifier C ~ D; or ... conjugates D;) declares a classifier as the conjugate of another, inheriting its features with directions reversed. The grammar rule ClassifierConjugation returns Conjugation — the declaring classifier is the conjugatedType, the target the originalType; there is no distinct ClassifierConjugation metaclass.
package P { classifier D; classifier C ~ D; }
Normative clauses: KerML §8.2.4.2.1
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Conjugation
KerML — kerml.structure.conjugation
A conjugation relationship (conjugates or the symbolic ~) makes a type inherit another’s features with input/output directions reversed. Lowers to a distinct Conjugation relationship (conjugatedType = the declaring type, originalType = the target). Both spellings are admitted; in lambda-parameter position only the conjugates keyword applies (the ~ there is the unary operator).
package P { class A; class X conjugates A; }
Normative clauses: KerML §8.3.3.1.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Connector
KerML — kerml.structure.connector
A connector is a feature that links two or more other features, typed by an association. It is the KerML base of SysML connection usages; its ends name the connected features.
package P { class C; feature x : C; feature y : C; connector c from x to y; }
Normative clauses: KerML §7.4.6
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Data Type
KerML — kerml.structure.data-type
A datatype is a classifier of data values — things distinguished only by their value, with no intrinsic identity, so two equal values are the same datum. It is the KerML base of SysML attribute value types.
package P { datatype D; }
Normative clauses: KerML §7.4.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Differencing
KerML — kerml.structure.differencing
A differencing relationship (differences) makes a type the difference of the listed types — the instances of the first that are not in the rest. Lowers to a distinct Differencing relationship owned by the declaring type.
package P { class A; class B; class D differences A, B; }
Normative clauses: KerML §8.3.3.1.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Disjoining
KerML — kerml.structure.disjoining
A disjoining relationship (disjoint from) asserts two types share no instances. Lowers to a distinct Disjoining relationship owned by the declaring type.
package P { class A; class B; class J disjoint from A, B; }
Normative clauses: KerML §8.3.3.1.4
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
EndFeatureMembership
KerML — kerml.structure.end-feature-membership
The EndFeatureMembership metamodel relationship (KerML abstract syntax). It has no dedicated concrete notation — it is how the model graph is wired — so it is documented as a metamodel concept with its inheritance and property constraints, not a grammar card.
Normative clauses: KerML §8.3.3.3.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Feature Conjugation
KerML — kerml.structure.feature-conjugation
A feature conjugation (feature b ~ a; or ... conjugates a;) declares a feature as the conjugate of another feature. The grammar rule FeatureConjugation returns Conjugation — the declaring feature is the conjugatedType, the target feature the originalType; there is no distinct FeatureConjugation metaclass.
package P { part def B { attribute a; attribute b ~ a; } }
Normative clauses: KerML §8.2.4.3.1
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Feature Inverting
KerML — kerml.structure.feature-inverting
A feature-inverting relationship (inverse of) asserts two features are inverses — each instance’s value under one is the reverse of the other. Lowers to a distinct FeatureInverting relationship owned by the declaring feature.
package P { feature f; feature g inverse of f; }
Normative clauses: KerML §8.3.3.3.6
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
FeatureMembership
KerML — kerml.structure.feature-membership
The FeatureMembership metamodel relationship (KerML abstract syntax). It has no dedicated concrete notation — it is how the model graph is wired — so it is documented as a metamodel concept with its inheritance and property constraints, not a grammar card.
Normative clauses: KerML §8.3.3.1.6
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Feature Typing
KerML — kerml.structure.feature-typing
A feature typing relates a feature to a type that classifies it — the relationship behind x : T. The same abstract-syntax relationship is realized by two divergent concrete syntaxes: KerML’s 'typing' typedFeature (':'|'typed' 'by') FeatureType versus SysML’s OwnedFeatureTyping | ConjugatedPortTyping dispatcher.
package P { part def A; part a : A; }
Normative clauses: KerML §8.3.3.3.7
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Related: Feature Typing
Filter Package
KerML — kerml.structure.filter-package
A filter package restricts which members are imported by a filter condition, written filter @Metadata inside the package. The condition lowers to a distinct ElementFilterMembership.
metadata def Safety; package P { filter @Safety; }
Normative clauses: KerML §8.3.4.13.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Import
KerML — kerml.structure.import
An import brings the members of another namespace into scope so their names resolve without full qualification. Visibility controls whether the imported names are re-exported.
package Lib { part def Widget; }
package App { import Lib::*; part w : Widget; }
Normative clauses: KerML §8.3.2.4.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Intersecting
KerML — kerml.structure.intersecting
An intersecting relationship (intersects) makes a type the intersection of the listed types — its instances are those common to all intersected types. Lowers to a distinct Intersecting relationship owned by the declaring type.
package P { class A; class B; class I2 intersects A, B; }
Normative clauses: KerML §8.3.3.1.7
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Library Package
KerML — kerml.structure.library-package
A library package is a package marked as a reusable model library with the library keyword. Its members are standard/shared definitions imported by other models; it lowers to a distinct LibraryPackage element.
library package Lib { part def P; }
Normative clauses: KerML §8.3.4.13.3
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Membership
KerML — kerml.structure.membership
The Membership metamodel relationship (KerML abstract syntax). It has no dedicated concrete notation — it is how the model graph is wired — so it is documented as a metamodel concept with its inheritance and property constraints, not a grammar card.
Normative clauses: KerML §8.3.2.4.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Membership Import
KerML — kerml.structure.membership-import
A membership import brings one named member of a namespace into scope, written import Path::member. It lowers to a distinct MembershipImport, in contrast to the wildcard namespace import.
package Q { part def X; } package R { import Q::X; }
Normative clauses: KerML §8.3.2.4.4
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Metaclass
KerML — kerml.structure.metaclass
A metaclass is a class whose instances are themselves model elements — the type of a metadata annotation. Metadata features are typed by metaclasses to attach structured data to the elements they annotate.
package P { metaclass M; }
Normative clauses: KerML §8.4.4.13.1
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Multiplicity
KerML — kerml.structure.multiplicity
A multiplicity bounds how many instances a feature may have, written as a range in square brackets. Bounds are a lower and an optional upper limit.
package P { part def A { attribute a[0..1]; } }
Normative clauses: KerML §8.3.3.1.9
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Namespace
KerML — kerml.structure.namespace
A namespace owns named members and defines the scope in which those names are visible and resolvable. Packages are the usual concrete namespaces a modeller writes; membership establishes the owned names.
package Ns { part def A; part def B; }
Normative clauses: KerML §8.3.2.4.5
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Namespace Import
KerML — kerml.structure.namespace-import
A namespace import brings every member of a namespace into scope, written import Path::*. It lowers to a distinct NamespaceImport and is the wildcard companion of the single-member membership import.
package Q { part def X; } package R { import Q::*; }
Normative clauses: KerML §8.3.2.4.6
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
OwningMembership
KerML — kerml.structure.owning-membership
The OwningMembership metamodel relationship (KerML abstract syntax). It has no dedicated concrete notation — it is how the model graph is wired — so it is documented as a metamodel concept with its inheritance and property constraints, not a grammar card.
Normative clauses: KerML §8.3.2.4.8
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Package
KerML — kerml.structure.package
A package is the concrete namespace a modeller writes to group related members and control their visibility. It owns named members and defines the scope in which those names resolve; membership within a package must be name-unique.
package P { class C; package Inner { class D; } }
Normative clauses: KerML §7.4.14
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Redefinition
KerML — kerml.structure.redefinition
A redefinition relates a feature to an inherited feature it replaces, so the redefining feature stands in for the original in its context — the redefines (or :>>) relationship. It is a specialized subsetting that also hides the redefined feature.
package P { type A; feature f : A; feature h : A redefines f; }
Normative clauses: KerML §8.3.3.3.8
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Specialization
KerML — kerml.structure.specialization
A specialization relates a more specific type to a more general one, so the specific type inherits the general type’s features. In usage syntax it is written with the subclassification operator.
package P { part def A; part def B :> A; }
Normative clauses: KerML §8.3.3.1.8
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Structure
KerML — kerml.structure.structure
A structure is a class whose instances are structural — composed of interconnected parts rather than behavioral occurrences. It is the KerML base from which SysML part definitions derive.
package P { struct S; }
Normative clauses: KerML §7.4.4
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Subsetting
KerML — kerml.structure.subsetting
A subsetting relates a feature to a more general feature whose values include all of the subsetting feature’s values — the subsets (or :>) relationship between features. It is the feature-level analogue of subclassification.
package P { type A; feature f : A; feature g : A subsets f; }
Normative clauses: KerML §8.3.3.3.10
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Type
KerML — kerml.structure.type
A type is the most general KerML classifier of things: it classifies a set of instances and may specialize other types, inheriting their features. Classes, datatypes, associations, and behaviors are all specialized kinds of type.
package P { type T; }
Normative clauses: KerML §7.3.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Type Featuring
KerML — kerml.structure.type-featuring
A type-featuring relationship (featured by) makes a feature a feature of another type. Lowers to a distinct TypeFeaturing relationship owned by the declaring feature.
package P { class A; feature g featured by A; }
Normative clauses: KerML §8.3.3.3.11
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Unioning
KerML — kerml.structure.unioning
A unioning relationship (unions) makes a type the union of the listed types — its instances are those of any unioned type. Lowers to a distinct Unioning relationship owned by the declaring type.
package P { class A; class B; class U unions A, B; }
Normative clauses: KerML §8.3.3.1.11
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Allocation Definition
SysML — sysml.structure.allocation-definition
An allocation definition, written allocation def, defines a reusable kind of allocation relating a source element to a target element. It lowers to a distinct AllocationDefinition.
allocation def AD { }
Normative clauses: SysML §7.15.2
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Allocation Usage
SysML — sysml.structure.allocation-usage
An allocation usage records that one element is allocated to another — a mapping decision (for example a function to a component) pending refinement into a firmer relationship. It has two or more ends and is typed by an allocation definition.
package P { part def Sys { part a; part b; allocation al allocate a to b; } }
Normative clauses: SysML §8.3.15.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Attribute Definition
SysML — sysml.structure.attribute-definition
An attribute definition defines a kind of value-typed feature that carries data rather than identity. Usages typed by it hold quantities or literals.
package P { attribute def Temperature; attribute t : Temperature; }
Normative clauses: SysML §8.3.7.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Attribute Usage
SysML — sysml.structure.attribute-usage
An attribute usage is a value-typed feature carrying data — a quantity or literal — rather than identity. It is typed by an attribute definition or a value type.
package P { attribute def Temperature; attribute t : Temperature; }
Normative clauses: SysML §8.3.7.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Binding Connector (as Usage)
SysML — sysml.structure.binding-connector-as-usage
A binding connector as usage, written bind a = b, asserts that two features have the same value at all times. It lowers to a distinct BindingConnectorAsUsage — the SysML usage form of a KerML binding connector.
part def P { attribute a; attribute b; bind a = b; }
Normative clauses: SysML §8.3.13.2
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Conjugated Port Definition
SysML — sysml.structure.conjugated-port-definition
A conjugated port definition is the input/output-reversed counterpart of a port definition, referenced with the ~ prefix (port p : ~P). It lowers to a distinct ConjugatedPortDefinition whose directions are flipped from the base port.
port def Pt { out item a; } part def Q { port p : ~Pt; }
Normative clauses: SysML §7.12.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Connection Definition
SysML — sysml.structure.connection-definition
A connection definition defines a reusable kind of connection between parts, with two or more ends. Connection usages instantiate it to wire specific parts together.
package P { connection def Link; }
Normative clauses: SysML §8.3.13.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Connection Usage
SysML — sysml.structure.connection-usage
A connection usage wires two or more parts together through connector ends. It is owned by a containing type (a part or action), not directly by a package, and instantiates a connection definition or the base association.
package P { part def Sys { part a; part b; connection c connect a to b; } }
Normative clauses: SysML §8.3.13.4
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Dependency
SysML — sysml.structure.dependency
A dependency declares that one or more client elements depend on one or more supplier elements — a coarse traceability link. It is a cross-grammar concept declared identically in KerML and SysML.
package P { part def A; part def B; dependency A to B; }
Normative clauses: SysML §7.3.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Enumerated Value
SysML — sysml.structure.enumerated-value
An enumerated value is the member form inside an enumeration definition. The spec models it as an EnumerationUsage (the grammar rule EnumeratedValue returns EnumerationUsage), so both the bare-name value form and the enum <name> form lower to an EnumerationUsage via a VariantMembership — there is no distinct EnumeratedValue metaclass.
package P { enum def Priority { high; low; } }
Normative clauses: SysML §7.8.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Enumeration Definition
SysML — sysml.structure.enumeration-definition
An enumeration definition defines a closed set of named literal values. Each enumerated value is a singleton usage of the definition, and a typed usage may take only one of them.
package P { enum def Color { enum red; enum green; } }
Normative clauses: SysML §8.3.8.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Enumeration Usage
SysML — sysml.structure.enumeration-usage
An enumeration usage is an attribute usage whose type is an enumeration definition. Inside an enum def body, enum <name>; declares an enumerated value (owned through a VariantMembership); standalone, enum <name> : <EnumDef>; declares an ordinary usage typed by the enumeration (plain owned member — the VariantMembership wrapper is enum-def-body-only). Both lower to a distinct EnumerationUsage.
package P { enum def Color { enum red; enum green; } }
Normative clauses: SysML §8.3.8.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Event Occurrence Usage
SysML — sysml.structure.event-occurrence-usage
An event occurrence usage marks a point event within an enclosing occurrence’s life — the happening of an occurrence, rather than the occurrence itself. It is written with the event keyword and either references an existing occurrence or declares one inline.
package P { occurrence def O { event occurrence e; } }
Normative clauses: SysML §8.3.9.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Feature Typing
SysML — sysml.structure.feature-typing
A feature typing relates a feature to a type that classifies it — the relationship behind x : T. The same abstract-syntax relationship is realized by two divergent concrete syntaxes: KerML’s 'typing' typedFeature (':'|'typed' 'by') FeatureType versus SysML’s OwnedFeatureTyping | ConjugatedPortTyping dispatcher.
package P { part def A; part a : A; }
Normative clauses: KerML §8.3.3.3.7
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Related: Feature Typing
Flow Connection
SysML — sysml.structure.flow-connection
A flow connection transfers items from a source port or feature to a compatible target across a connection. Endpoints must be reachable and their item types compatible.
package P { part def A { out port o; } part def B { in port i; } part def Sys { part a : A; part b : B; flow a.o to b.i; } }
Normative clauses: SysML §8.3.16.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Flow Definition
SysML — sysml.structure.flow-definition
A flow definition, written flow def, defines a reusable kind of transfer of items between features. It lowers to a distinct FlowDefinition that flow usages can be typed by.
flow def FD { }
Normative clauses: SysML §8.3.16.2
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Individual Definition
SysML — sysml.structure.individual-definition
An individual definition (individual def X;) defines a class constrained to represent at most one individual over its lifetime. The grammar rule IndividualDefinition returns OccurrenceDefinition with isIndividual = true — there is no distinct IndividualDefinition metaclass. It lowers to an OccurrenceDefinition carrying the flag (the spec’s owned empty-multiplicity member is consciously carried by the flag alone).
package P { individual def X; abstract individual def Y :> X; }
Normative clauses: SysML §7.9.4
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Individual Usage
SysML — sysml.structure.individual-usage
An individual usage (the individual prefix) names a single identified occurrence. The spec models it as an OccurrenceUsage with isIndividual = true; it lowers to an OccurrenceUsage carrying that flag — there is no distinct IndividualUsage metaclass.
package P { occurrence def D; individual d : D; }
Normative clauses: SysML §7.9.4
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Interface Definition
SysML — sysml.structure.interface-definition
An interface definition defines a reusable kind of connection between ports, coupling their directed features. Interface usages instantiate it between specific ports.
package P { interface def Ifc; }
Normative clauses: SysML §8.3.14.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Interface Usage
SysML — sysml.structure.interface-usage
An interface usage connects two ports through interface ends, coupling their directed features. Like a connection it must be owned by a containing type, not directly by a package, and instantiates an interface definition.
package P { port def Sig; part def Sys { part x { port p1 : Sig; } part y { port p2 : Sig; } interface i connect x.p1 to y.p2; } }
Normative clauses: SysML §8.3.14.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Item Definition
SysML — sysml.structure.item-definition
An item definition defines a kind of thing that can be created, stored, or moved through a system — items carry identity but need not be behavioral parts. Usages typed by it occupy structure or flow along connections.
package P { item def Widget; }
Normative clauses: SysML §8.3.10.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Item Usage
SysML — sysml.structure.item-usage
An item usage is a typed occurrence of an item within a structure or flow. It is typed by an item definition and may be nested in a part or carried by a flow connection.
package P { item def Widget; item w : Widget; }
Normative clauses: SysML §8.3.10.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Occurrence Definition
SysML — sysml.structure.occurrence-definition
An occurrence definition defines a kind of thing that exists over time and space — the common base of parts (structure) and actions (behavior).
package P { occurrence def Event; }
Normative clauses: SysML §8.3.9.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Occurrence Usage
SysML — sysml.structure.occurrence-usage
An occurrence usage is a typed occurrence within a model’s space-time. It is typed by an occurrence definition and may be portioned in time or space.
package P { occurrence def Event; occurrence e : Event; }
Normative clauses: SysML §8.3.9.4
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Part Definition
SysML — sysml.structure.part-definition
A part definition defines a reusable kind of system component with structure and behavior. Part usages instantiate it within a containing structure.
package P { part def Engine; }
Normative clauses: SysML §8.3.11.2
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Part Usage
SysML — sysml.structure.part-usage
A part usage is a typed occurrence of a part within a containing structure. It is typed by a part definition and may nest further usages or redefine inherited ones.
package P { part def Engine; part e : Engine; }
Normative clauses: SysML §8.3.11.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Port Definition
SysML — sysml.structure.port-definition
A port definition defines a reusable kind of interaction point through which items and information pass. Port usages instantiate it on parts, optionally conjugated to reverse direction.
package P { port def Fuel; }
Normative clauses: SysML §8.3.12.5
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Port Usage
SysML — sysml.structure.port-usage
A port usage is a typed interaction point on a part through which items and information pass. It is typed by a port definition and may be conjugated to reverse direction.
package P { port def Fuel; part def E { port p : Fuel; } }
Normative clauses: SysML §8.3.12.6
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Portion Usage
SysML — sysml.structure.portion-usage
A portion usage (snapshot or timeslice) denotes a temporal portion of an occurrence. The spec models it as an OccurrenceUsage carrying a PortionKind; it lowers to an OccurrenceUsage with that portion kind — there is no distinct PortionUsage metaclass.
package P { occurrence def D; occurrence o : D { snapshot s; } }
Normative clauses: SysML §8.3.9.5
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Reference Usage
SysML — sysml.structure.reference-usage
A reference usage is a non-composite feature that refers to something owned elsewhere, rather than composing it. The ref keyword marks a feature as referential.
package P { part def W { ref r; } }
Normative clauses: SysML §8.3.6.3
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Validation Rules
Generated from the sysml-rs language pack — see the Language Reference index for provenance, licensing, and the raw JSON. 76 cards.
At Most One Return Parameter
KerML — kerml.validation.at-most-one-return-parameter
A function can have at most one return parameter
Normative clauses: KerML §8.3.4.7.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Function
Behavior Not Specialize Structure
KerML — kerml.validation.behavior-not-specialize-structure
A behavior cannot specialize a structure
Normative clauses: KerML §8.3.4.6.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Behavior
Binary Connector Two Ends
KerML — kerml.validation.binary-connector-two-ends
A binary connector must have exactly two end features
Normative clauses: KerML §8.3.4.5.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Binding Connector Two Ends
KerML — kerml.validation.binding-connector-two-ends
A binding connector must have exactly two end features
Normative clauses: KerML §8.3.4.5.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Class Not Specialize Datatype
KerML — kerml.validation.class-not-specialize-datatype
A class cannot specialize a data type or association
Normative clauses: KerML §8.3.4.2.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Class
Connector Owned By Type
KerML — kerml.validation.connector-owned-by-type
A connection must be owned by a type (part, action, etc.), not a package
Normative clauses: KerML §8.3.4.5.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Connection Usage, Flow Connection, Interface Usage
Datatype Not Specialize Class
KerML — kerml.validation.datatype-not-specialize-class
A data type cannot specialize a class or association
Normative clauses: KerML §8.3.4.1.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Data Type
Model Element Must Be Owned
KerML — kerml.validation.model-element-must-be-owned
Every non-root model element is owned by (a member of) a namespace; a bare top-level usage/def that is not a package is an orphan.
Normative clauses: KerML §8.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
No Inherited Name Conflict
KerML — kerml.validation.no-inherited-name-conflict
Member name ‘{name}’ conflicts with inherited member
Normative clauses: KerML §8.3.2.4.5
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Namespace
No Name Alias Conflict
KerML — kerml.validation.no-name-alias-conflict
Member name ‘{name}’ duplicates an alias
Normative clauses: KerML §8.3.2.4.5
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Namespace
Parameter Membership Owning Type
KerML — kerml.validation.parameter-membership-owning-type
Parameter membership is only allowed in behaviors and steps
Normative clauses: KerML §8.3.4.6.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Result Expression In Function Or Expression
KerML — kerml.validation.result-expression-in-function-or-expression
Result expression membership is only allowed in functions and expressions
Normative clauses: KerML §8.3.4.7.7
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Return Parameter Membership Owning Type
KerML — kerml.validation.return-parameter-membership-owning-type
Return parameter membership is only allowed in functions and expressions
Normative clauses: KerML §8.3.4.7.8
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Structure Not Specialize Behavior
KerML — kerml.validation.structure-not-specialize-behavior
A structure cannot specialize a behavior
Normative clauses: KerML §8.3.4.3.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Structure
Succession Two Ends
KerML — kerml.validation.succession-two-ends
A succession must have exactly two end features
Normative clauses: KerML §8.3.4.5.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Unique Owned Member Names
KerML — kerml.validation.unique-owned-member-names
Duplicate owned member name ‘{name}’
Normative clauses: KerML §8.3.2.4.5
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Namespace
Accept Action Has Payload
SysML — sysml.validation.accept-action-has-payload
An accept action must have a payload parameter
Normative clauses: SysML §8.3.17.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Accept Action
Action Typed By Behavior
SysML — sysml.validation.action-typed-by-behavior
An action must be typed by action definitions
Normative clauses: SysML §8.3.17.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Action Usage
Actor Membership In Req Or Case
SysML — sysml.validation.actor-membership-in-req-or-case
Only requirements and cases can have actors
Normative clauses: SysML §8.3.21.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Actor
Allocation Has Ends
SysML — sysml.validation.allocation-has-ends
An allocation must have at least two end features
Normative clauses: SysML §8.3.15.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Allocation Usage
Allocation Typed By Allocation Defs
SysML — sysml.validation.allocation-typed-by-allocation-defs
An allocation must be typed by allocation definitions
Normative clauses: SysML §8.3.15.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Allocation Usage
Analysis Case At Most One Subject
SysML — sysml.validation.analysis-case-at-most-one-subject
An analysis case can have at most one subject
Normative clauses: SysML §8.3.23.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Analysis Case, Analysis Case Definition
Assignment Action Has Target
SysML — sysml.validation.assignment-action-has-target
An assignment action must have a target feature
Normative clauses: SysML §8.3.17.5
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Assignment Action
At Most One Objective
SysML — sysml.validation.at-most-one-objective
A case definition can have at most one objective
Normative clauses: SysML §8.3.22.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Case Definition, Case Usage
At Most One State Subaction
SysML — sysml.validation.at-most-one-state-subaction
A state definition may have at most one entry, one do, and one exit action
Normative clauses: SysML §8.3.18.5
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: State Definition, State Usage
At Most One Subject
SysML — sysml.validation.at-most-one-subject
A requirement definition can have at most one subject
Normative clauses: SysML §8.3.21.8
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Case Definition, Case Usage, Requirement Definition, Requirement Usage
At Most One View Rendering
SysML — sysml.validation.at-most-one-view-rendering
A view definition may have at most one view rendering
Normative clauses: SysML §8.3.26.7
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: View Definition, View Usage
Attribute Def Not Specialize Item Def
SysML — sysml.validation.attribute-def-not-specialize-item-def
An attribute definition cannot specialize an item definition
Normative clauses: SysML §8.3.7.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Attribute Definition
Attribute Must Not Be Composite
SysML — sysml.validation.attribute-must-not-be-composite
An AttributeUsage must not be composite
Normative clauses: SysML §8.3.7.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Attribute Definition, Attribute Usage
Attribute Typed By Datatypes
SysML — sysml.validation.attribute-typed-by-datatypes
An attribute must be typed by attribute definitions
Normative clauses: SysML §8.3.7.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Attribute Usage
Calculation Typed By One Calc Def
SysML — sysml.validation.calculation-typed-by-one-calc-def
A calculation must be typed by one calculation definition
Normative clauses: SysML §8.3.19.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Calculation Usage
Case Typed By One Case Def
SysML — sysml.validation.case-typed-by-one-case-def
A case must be typed by one case definition
Normative clauses: SysML §8.3.22.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Case Usage
Concern At Most One Subject
SysML — sysml.validation.concern-at-most-one-subject
A concern can have at most one subject
Normative clauses: SysML §8.3.21.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Concern Definition, Concern Usage
Connection Has Ends
SysML — sysml.validation.connection-has-ends
A connection must have at least two end features
Normative clauses: SysML §8.3.13.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Connection Usage
Connection Typed By Association
SysML — sysml.validation.connection-typed-by-association
A connection must be typed by connection definitions
Normative clauses: SysML §8.3.13.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Connection Usage
Constraint Typed By Predicate
SysML — sysml.validation.constraint-typed-by-predicate
A constraint must be typed by one constraint definition
Normative clauses: SysML §8.3.20.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Constraint Usage
Decision Node One Incoming
SysML — sysml.validation.decision-node-one-incoming
A decision node must have at most one incoming succession
Normative clauses: SysML §8.3.17.7
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Decision Node
Enumeration Typed By One Enum Def
SysML — sysml.validation.enumeration-typed-by-one-enum-def
An enumeration must be typed by one enumeration definition
Normative clauses: SysML §8.3.8.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Enumeration Usage
Exhibit State One Type
SysML — sysml.validation.exhibit-state-one-type
An exhibit state must be typed by exactly one state definition
Normative clauses: SysML §8.3.18.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Exhibit State Usage
Flow Typed By Interaction
SysML — sysml.validation.flow-typed-by-interaction
A flow connection must be typed by flow connection definitions
Normative clauses: SysML §8.3.16.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Flow Connection
Fork Node One Incoming
SysML — sysml.validation.fork-node-one-incoming
A fork node must have at most one incoming succession
Normative clauses: SysML §8.3.17.8
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Fork Node
Interface Has Ends
SysML — sysml.validation.interface-has-ends
An interface must have at least two end features
Normative clauses: SysML §8.3.14.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Interface Usage
Interface Typed By Interface Defs
SysML — sysml.validation.interface-typed-by-interface-defs
An interface must be typed by interface definitions
Normative clauses: SysML §8.3.14.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Interface Usage
Item Def Not Specialize Attribute Def
SysML — sysml.validation.item-def-not-specialize-attribute-def
An item definition cannot specialize an attribute definition
Normative clauses: SysML §8.3.10.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Item Definition
Item Typed By Item Defs
SysML — sysml.validation.item-typed-by-item-defs
An item must be typed by item definitions
Normative clauses: SysML §8.3.10.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Item Usage
Join Node One Outgoing
SysML — sysml.validation.join-node-one-outgoing
A join node must have at most one outgoing succession
Normative clauses: SysML §8.3.17.11
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Join Node
Merge Node One Outgoing
SysML — sysml.validation.merge-node-one-outgoing
A merge node must have at most one outgoing succession
Normative clauses: SysML §8.3.17.13
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Merge Node
Objective Membership In Case
SysML — sysml.validation.objective-membership-in-case
Only cases can have objectives
Normative clauses: SysML §8.3.22.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Objective
Occurrence Typed By Occurrence Defs
SysML — sysml.validation.occurrence-typed-by-occurrence-defs
An occurrence must be typed by occurrence definitions
Normative clauses: SysML §8.3.9.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Occurrence Usage
Parallel State No Transitions
SysML — sysml.validation.parallel-state-no-transitions
A parallel state cannot have transitions
Normative clauses: SysML §8.3.18.5
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: State Definition, State Usage
Part Typed By Part Defs
SysML — sysml.validation.part-typed-by-part-defs
A part must be typed by at least one part definition
Normative clauses: SysML §8.3.11.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Part Usage
Perform Action One Type
SysML — sysml.validation.perform-action-one-type
A perform action must be typed by exactly one action definition
Normative clauses: SysML §8.3.17.14
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Perform Action
Port Definition Owned Usages Referential
SysML — sysml.validation.port-definition-owned-usages-referential
The non-port owned usages of a port definition must be referential (non-composite)
Normative clauses: SysML §8.3.12.5
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Port Definition
Port Typed By Port Defs
SysML — sysml.validation.port-typed-by-port-defs
A port must be typed by port definitions
Normative clauses: SysML §8.3.12.6
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Port Usage
Port Usage Nested Usages Referential
SysML — sysml.validation.port-usage-nested-usages-referential
The non-port nested usages of a port usage must be referential (non-composite)
Normative clauses: SysML §8.3.12.6
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Port Usage
Requirement Constraint In Requirement
SysML — sysml.validation.requirement-constraint-in-requirement
Only requirements can have assumed or required constraints
Normative clauses: SysML §8.3.21.7
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Requirement Constraint (assume / require)
Requirement Constraints Composite
SysML — sysml.validation.requirement-constraints-composite
Assumed constraints must be composite
Normative clauses: SysML §8.3.21.8
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Requirement Definition, Requirement Usage
Requirement Typed By One Req Def
SysML — sysml.validation.requirement-typed-by-one-req-def
A requirement must be typed by one requirement definition
Normative clauses: SysML §8.3.21.9
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Requirement Usage
Satisfy Req One Type
SysML — sysml.validation.satisfy-req-one-type
A satisfy requirement must be typed by one requirement definition
Normative clauses: SysML §8.3.21.10
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Requirement Satisfaction
Send Action Has Payload
SysML — sysml.validation.send-action-has-payload
A send action must have a payload parameter
Normative clauses: SysML §8.3.17.15
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Send Action
Stakeholder Membership In Requirement
SysML — sysml.validation.stakeholder-membership-in-requirement
Only requirements can have stakeholders
Normative clauses: SysML §8.3.21.12
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Stakeholder
State Subaction Owned By State
SysML — sysml.validation.state-subaction-owned-by-state
Entry, do, and exit actions can only appear in state definitions or usages
Normative clauses: SysML §8.3.18.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
State Typed By State Defs
SysML — sysml.validation.state-typed-by-state-defs
A state must be typed by state definitions
Normative clauses: SysML §8.3.18.6
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: State Usage
Subject Is First Parameter
SysML — sysml.validation.subject-is-first-parameter
The subject must be the first parameter (input) of a requirement definition
Normative clauses: SysML §8.3.21.8
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Case Definition, Case Usage, Requirement Definition, Requirement Usage
Subject Membership In Req Or Case
SysML — sysml.validation.subject-membership-in-req-or-case
Only requirements and cases can have subjects
Normative clauses: SysML §8.3.21.11
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Subject
Transition Feature In Transition
SysML — sysml.validation.transition-feature-in-transition
Transition feature membership is only allowed in transitions
Normative clauses: SysML §8.3.18.8
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Transition Has Source
SysML — sysml.validation.transition-has-source
A transition must have a source
Normative clauses: SysML §8.3.18.9
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Transition Usage
Transition Owned By State Or Action
SysML — sysml.validation.transition-owned-by-state-or-action
A transition must be owned by a state or an action
Normative clauses: SysML §8.3.18.9
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Transition Usage
Usage Typed By Definitions
SysML — sysml.validation.usage-typed-by-definitions
A usage must be typed by definitions
Normative clauses: SysML §8.3.6.4
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Use Case At Most One Subject
SysML — sysml.validation.use-case-at-most-one-subject
A use case can have at most one subject
Normative clauses: SysML §8.3.25.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Use Case Definition, Use Case Usage
Variant Membership In Variation
SysML — sysml.validation.variant-membership-in-variation
A variant must be an owned member of a variation
Normative clauses: SysML §8.3.6.5
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Variation Members Are Variants
SysML — sysml.validation.variation-members-are-variants
An owned usage of a variation must be a variant
Normative clauses: SysML §8.3.6.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Variation Must Be Abstract
SysML — sysml.validation.variation-must-be-abstract
A variation definition must be abstract
Normative clauses: SysML §8.3.6.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Variation No Chain
SysML — sysml.validation.variation-no-chain
A variation must not specialize another variation
Normative clauses: SysML §8.3.6.2
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Verification Case At Most One Subject
SysML — sysml.validation.verification-case-at-most-one-subject
A verification case can have at most one subject
Normative clauses: SysML §8.3.24.3
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Related: Verification Case, Verification Case Definition
View Rendering In View
SysML — sysml.validation.view-rendering-in-view
Only views can have view renderings
Normative clauses: SysML §8.3.26.10
Support (sysml-rs): parse unknown · resolve unknown · elaborate unknown · execute unknown
Views
Generated from the sysml-rs language pack — see the Language Reference index for provenance, licensing, and the raw JSON. 8 cards.
Membership Expose
SysML — sysml.views.membership-expose
A membership expose imports a single named member of a namespace into a view. It is written with expose and a qualified name naming that one member.
package P { part def Car { part e; } view v { expose Car::e; } }
Normative clauses: SysML §8.3.26.3
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Namespace Expose
SysML — sysml.views.namespace-expose
A namespace expose imports every member of a namespace into a view, so the whole namespace is available to be rendered. It is written with expose and a namespace path ending in ::*.
package P { part def Car { part e; } view v { expose Car::*; } }
Normative clauses: SysML §8.3.26.4
Support (sysml-rs): parse ✓ · resolve unknown · elaborate unknown · execute unknown
Rendering Definition
SysML — sysml.views.rendering-definition
A rendering definition defines how a view is presented — the notation or format used to draw it. Rendering usages bind a rendering to a view.
package P { rendering def AsTree; }
Normative clauses: SysML §8.3.26.5
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Rendering Usage
SysML — sysml.views.rendering-usage
A rendering usage is a typed occurrence of a rendering, typed by a rendering definition. It selects the presentation applied to the view that owns it.
package P { rendering def Rn; rendering rn : Rn; }
Normative clauses: SysML §8.3.26.6
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
View Definition
SysML — sysml.views.view-definition
A view definition defines a reusable kind of view — a rendered projection of the model for a stakeholder, governed by a viewpoint. View usages instantiate it.
package P { view def Overview; }
Normative clauses: SysML §8.3.26.7
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
View Usage
SysML — sysml.views.view-usage
A view usage renders a selection of the model according to a viewpoint, exposing chosen elements to a stakeholder. It is typed by a view definition.
package P { view def V; view v : V; }
Normative clauses: SysML §8.3.26.11
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Viewpoint Definition
SysML — sysml.views.viewpoint-definition
A viewpoint definition defines the concerns a view must address for a set of stakeholders — the specification a conforming view satisfies. Viewpoint usages bind it to concrete views.
package P { viewpoint def Stakeholder; }
Normative clauses: SysML §8.3.26.8
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown
Viewpoint Usage
SysML — sysml.views.viewpoint-usage
A viewpoint usage is a typed occurrence of a viewpoint, typed by a viewpoint definition. It applies the viewpoint’s concerns to the view that owns it.
package P { viewpoint def Vp; viewpoint vp : Vp; }
Normative clauses: SysML §8.3.26.9
Support (sysml-rs): parse ✓ · resolve ✓ · elaborate unknown · execute unknown