MBSE iNsightsMBSE iNsights

· 7 min read

The Derivation Ladder

Eighteen constructs, added to a use case one at a time, and exactly what each one turns into downstream. The map nobody prints, with the real numbers attached.

Ask what a use case is and you get a definition. Ask what a use case costs and nobody has a number.

That second question is the practical one. When you add a decision to a model, something happens downstream — more states, more requirements, more tests, possibly a probe somebody now has to build. Most teams find out how much at integration, which is the most expensive possible moment to find out.

So here is the ladder: eighteen constructs, added one at a time to a single small use case, with the real derived output at every rung. Nothing below is estimated. Each figure comes from running the transformation and reading what came out.

The whole map, on one page

You writeYou get
action 'X'a state, a requirement, a verification case, a test
first A then Ba transition, a WHEN trigger, an assume stateBefore == clause
out p : Tan evidence attribute and a require constraint
use-case-level out + flowflips that evidence to boundary-observable
decide + if/elseguarded transitions; guards become assume clauses
fork / joinall branches run; the machine serializes them and says so
actor + a flow across itan interface requirement and a controllable stimulus
accept X via porta waiting state, an item trigger, a port, a driver method
accept after 30 [SI::s]a time trigger, carried through verbatim; the test elapses it
accept when a >= ba change trigger on the transition
any other behaviora completion trigger, named so it can be referenced
send X via port to actora boundary crossing and an interface requirement
#Exception + #ClosedWorlda prohibition and a negative test
#Terminala distinct useCaseFailed terminal state
constraint def <T>Validsharp predicates instead of a bare presence check
loop … until cthe exit bound, preserved as a constraint
a guard on something nothing producesnothing — plus a loud diagnostic

That last row is the one worth staring at. Everything else on this list produces something. One thing produces a complaint instead, and it is the row that matters most.

What it costs, rung by rung

What one more construct costs

Running totals as each construct is added to a single use case. Hover any step.

  • Requirements
  • Verification cases
  • States
  • Probes needed
048121615 Requirements9 Probes needed0. One actionRequirements3Verification cases2States2Probes needed41. A second action, in sequenceRequirements4Verification cases3States3Probes needed52. An outputRequirements4Verification cases3States3Probes needed53. A declared outcomeRequirements4Verification cases3States3Probes needed44. A supplied inputRequirements5Verification cases4States3Probes needed45. A decisionRequirements6Verification cases5States4Probes needed56. An actor at the boundaryRequirements8Verification cases6States5Probes needed57. An awaited eventRequirements10Verification cases7States6Probes needed58. A timeout and a change eventRequirements12Verification cases9States8Probes needed69. An error pathRequirements13Verification cases10States8Probes needed610. A failure shape, declared onceRequirements13Verification cases10States8Probes needed611. A dead endRequirements13Verification cases10States9Probes needed612. What must hold before and afterRequirements14Verification cases10States9Probes needed813. What 'good data' meansRequirements13Verification cases10States9Probes needed614. RepetitionRequirements13Verification cases10States9Probes needed615. A held state — undeclaredRequirements14Verification cases11States10Probes needed716. …declared, and it resolvesRequirements14Verification cases11States10Probes needed817. Two things at onceRequirements15Verification cases12States11Probes needed90 — one action17 — two things at once
Table view
#Construct addedRequirementsVerification casesStatesProbes needed
0One action3224
1A second action, in sequence4335
2An output4335
3A declared outcome4334
4A supplied input5434
5A decision6545
6An actor at the boundary8655
7An awaited event10765
8A timeout and a change event12986
9An error path131086
10A failure shape, declared once131086
11A dead end131096
12What must hold before and after141098
13What 'good data' means131096
14Repetition131096
15A held state — undeclared1411107
16…declared, and it resolves1411108
17Two things at once1512119

Two things in that chart are worth naming before anything else.

Requirements outrun states. By the top of the ladder there are 15 requirements over 11 states. Constructs like preconditions and acceptance criteria add obligations without adding behavior, so the two curves separate. Counting states and calling it scope will understate the work.

The probe line does not only go up. It falls at step 3 and again at step 13. Those are the two rungs where something became observable at the boundary — a declared outcome, and acceptance criteria on an item type. Every fact that crosses the boundary is a fact nobody has to build instrumentation to see. That is the whole economics of testability in one wiggle: modeling effort spent in the right place removes downstream engineering, and you can watch it happen.

One structural note, since the chart quietly depends on it. In this ladder, states and transitions are equal at every rung, and so are verification cases and tests. Plotting all four would draw two pairs of identical lines. The one-to-one between a verification case and a test is by construction — the generator emits one per obligation. The states-to-transitions equality is a property of this particular ladder, not a law; a fork or a rejoining error path will break it.

Rungs 0 to 4 — the parts everyone already writes

The bottom of the ladder is the vocabulary every use case already uses, and it is where the one-in-several-out ratio is established.

package Ladder {
    use case <'UC-1'> 'Submit Order' {
        subject app :> Arch::orderService {
            perform 'Record Order';
        }
        action 'Record Order';
        first start then 'Record Order';
        first 'Record Order' then done;
    }
}

One action. Downstream: two states (recordOrder, useCaseCompleted), four evidence attributes, three requirements, a verification case, a test, and five driver methods the harness will need. That ratio holds all the way up.

Add first A then B and the succession becomes a transition, a WHEN trigger in the requirement prose, and an assume stateBefore == … clause. That last one is the quiet payoff: the test now has to reach a starting state before it acts, so ordering is proven rather than assumed.

Then out p : Boolean becomes an evidence attribute and a require constraint — but an internal one, needing a probe. Promote it to a use-case-level output with a flow and the same fact flips to boundary-observable, and the probe count drops. Same information, different reachability, real money.

Rungs 5 to 8 — where the boundary appears

A decision produces guarded transitions, and the guards become assume clauses so each branch’s test only claims to have exercised its own branch.

An actor at the boundary is the first construct that makes the use case drivable. Every event the use case waits for, and every action a human performs, is a place a harness can push. A use case with neither can only be started and watched.

Then triggers get specific:

accept OrderCommand via app.commandPort   // an item
accept after 30 [SI::s]                   // the clock
accept when balance >= total              // a condition becoming true

Every transition names what fires it. The timeout is carried through verbatim, units and all30 [SI::s] arrives in the test as an elapse instruction, not as a number somebody retyped. Anything without an explicit trigger gets a completion trigger, named so it can still be referenced.

Rungs 9 to 12 — saying what must not happen

This is the part of the ladder that a use case cannot reach on its own.

A use case is existential: there is a path where this happens. A prohibition is universal: this never happens. No transformation derives the second from the first, so you have to draw the failure branch and then declare the model closed:

#Exception #Terminal
action 'Reject Order' { out rejectionRecorded : Boolean; }

Mark the branch #Exception, declare #ClosedWorld, and the prohibition appears — with a negative test that exercises the failure deliberately rather than never. Step 9 is the only rung on the whole ladder where the prohibition count moves off zero.

Two refinements follow. The keywords are semantic metadata, not decoration: #Exception action 'Reject' states that the action is an ExceptionBehavior, which means you can say it once on a definition and have every usage inherit it. And #Terminal routes the failure to a distinct useCaseFailed state rather than folding it into useCaseCompleted — because “the use case stopped” and “the use case succeeded” are different claims, and a test that cannot tell them apart is not testing much.

Rungs 13 to 17 — the four things only you can say

The top of the ladder is where the model stops being able to guess.

What good data looks like. A use case types its data and never says what makes an instance acceptable. Declare constraint def <T>Valid once per item type and every parameter of that type gains real predicates. This is the rung that turns notEmpty(x) into something worth checking — and note on the chart that it is also where boundary-observability jumps from 3 to 6.

What the subject holds. Guard on a condition that persists across steps — a session is live, the store is open — and if you never declared it, the derivation produces nothing for that branch and says so loudly:

⚠︎ D17: Guard references a feature nothing in the model accounts for — most
likely a state the subject holds that was never declared. No condition was
derived for this branch.

The use case is in a state; the subject holds other states. A held state is never inferred, because first A then B says nothing about which of A’s effects survive into B. Declare it as one attribute with an initial value — nothing in a flow says what the subject starts at — and the guard resolves.

That both branches ran. A decision runs one branch; a fork runs all of them. But a state machine is in one state at a time, so a fork has to be serialized into an order the use case never stated — and that gets a diagnostic too:

ⓘ D20: A fork was serialized. A state machine is in one state at a time, so
concurrent branches were put in a fixed order the use case did not state.

The requirement over the join stays order-free — WHEN Await Settlement and Notify Customer have completed — which is the honest statement. The serialized order is an artifact of the state machine, and the tool says which order it picked rather than letting you discover it later.

What the ladder is actually for

Read top to bottom, this is a costing table: it tells you what a construct buys and what it charges.

Read a different way, it is a checklist for a model you already have. Ten of these eighteen rungs are things most use cases in the wild simply do not have — no acceptance criteria, no declared outputs, no failure branches, no closed-world declaration. A model missing all of them still parses, still reviews well, and still yields requirements. It just yields requirements that no black-box test could ever settle.

The two loud diagnostics are the point of the whole exercise. Everything else on the ladder produces an artifact. Those two produce a complaint, and a complaint is the only output that can tell you something you did not already know.

More of this, on video

Walkthroughs in Cameo and SysML v2, with the model on screen.

Subscribe on YouTube